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

Shim throws TypeInitializationException in Unity Engine #37

Open
sam-ln opened this issue Jan 30, 2024 · 17 comments
Open

Shim throws TypeInitializationException in Unity Engine #37

sam-ln opened this issue Jan 30, 2024 · 17 comments
Assignees
Labels
question/investigation Further information or investigation is required

Comments

@sam-ln
Copy link

sam-ln commented Jan 30, 2024

When trying to shim a static method, the execution of PoseContext.Isolate fails with a TypeInitializationException.

The class whose method I'm trying to shim:

public static class SimpleStaticClass
{
    public static double SimpleStaticMethod()
    {
        return 5;
    }
}

My test-class:

public class SimplePoseTest 
{
   [Test]
   public void SimpleTest()
   {
      Shim methodShim = Shim.Replace(() => SimpleStaticClass.SimpleStaticMethod()).With(
         () => (double)20);
      PoseContext.Isolate( () => 
      {
        //doing nothing yet
      }, methodShim);
   }
}

The stack-trace:

System.TypeInitializationException : The type initializer for 'Pose.Helpers.StubHelper' threw an exception.
  ----> System.Exception : Cannot get method GetMethodDescriptor from type DynamicMethod
---
at Pose.IL.MethodRewriter.Rewrite () [0x000b4] in <fdbdd2421b614a2cbd32b6336046a6a9>:0 
  at Pose.PoseContext.Isolate (System.Action entryPoint, Pose.Shim[] shims) [0x00058] in <fdbdd2421b614a2cbd32b6336046a6a9>:0 
  at SimplePoseTest.SimpleTest () [0x0004f] in E:\Unity Projects\Worlds\Misc\PoserUnitTesting\Assets\Tests\SimplePoseTest.cs:12 
  at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
  at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0006a] in <eef08f56e2e042f1b3027eca477293d9>:0 
--Exception
  at Pose.Helpers.StubHelper..cctor () [0x0001a] in <fdbdd2421b614a2cbd32b6336046a6a9>:0

More details:

The project is using the net48 Pose dll.
It is run from within Unity Engine
Issue appears in both 2.0 and 2.1 alpha

BTW: @Miista Thanks a lot for reviving this project! 👍

@sam-ln
Copy link
Author

sam-ln commented Jan 30, 2024

This might occur due to a misconfiguration in my project, leading to the dependency System.Reflection.Emit.Lightweight missing. Will investigate further.

@Miista Miista assigned sam-ln and unassigned sam-ln Jan 31, 2024
@Miista
Copy link
Owner

Miista commented Jan 31, 2024

I will test it out in the sandbox just to be sure.

UPDATE: So far the below sample executes without exception in the Sandbox.

Sample with target framework net48

namespace Pose.Sandbox
{
    public static class SimpleStaticClass
    {
        public static double SimpleStaticMethod()
        {
            return 5;
        }
    }
    
    public class Program
    {
        public static async Task<int> GetIntAsync()
        {
            Console.WriteLine("Here");
            return await Task.FromResult(1);
        }

        public static void Main(string[] args)
        {
            Shim methodShim = Shim.Replace(() => SimpleStaticClass.SimpleStaticMethod()).With(
                () => (double)20);
            PoseContext.Isolate( () => 
            {
                //doing nothing yet
            }, methodShim);
        }
    }
}

@Miista Miista added the question/investigation Further information or investigation is required label Jan 31, 2024
@Miista
Copy link
Owner

Miista commented Jan 31, 2024

@sam-ln: Let me know what you find out. Perhaps there is some way we can guard against this.

@sam-ln sam-ln changed the title Shim throws TypeInitializationException Shim throws TypeInitializationException in Unity Engine Jan 31, 2024
@sam-ln
Copy link
Author

sam-ln commented Jan 31, 2024

So, the issue doesn't seem to be related to a missing dependency. I can use System.Reflection.Emit.Lightweight.DynamicMethod in the project just fine. The Expection System.Exception : Cannot get method GetMethodDescriptor from type DynamicMethod made me take a look at the DynamicMethod Reference and there is in fact no method GetMethodDescriptor in DynamicMethod. Any thoughts?
I also added the info that my project is running from within Unity Engine to this issue as it might be relevant.

@sam-ln
Copy link
Author

sam-ln commented Jan 31, 2024

Oh, I see, it's a private method.

internal static class StubHelper
{
private static readonly MethodInfo GetMethodDescriptor =
typeof(DynamicMethod).GetMethod("GetMethodDescriptor", BindingFlags.Instance | BindingFlags.NonPublic)
?? throw new Exception($"Cannot get method GetMethodDescriptor from type {nameof(DynamicMethod)}");

In this static initializer you check whether DynamicMethod has a method GetMethodDescriptor and there it throws an Exception because for some reason it can't access it.

@sam-ln
Copy link
Author

sam-ln commented Jan 31, 2024

It seems that GetMethodDescriptor in DynamicMethod does have the [SecurityCritical]-Attribute. And beginning with .NET Framework 4, methods with this attribute cannot be retrieved by reflection in a lot of environments (transparent or partially-trusted-code).

@Miista
Copy link
Owner

Miista commented Jan 31, 2024

@sam-ln That's weird because we have a test for specifically this scenario. Please see StubHelperTests.Can_get_method_pointer.

That said, I can see that the method is actually missing on .NET 5. Never mind. I found it.

@sam-ln You mentioned that methods cannot be retrieved in a lot of environments. Could it be that this is a Unity specific issue?
If we could find some way to run the test suite against Unity, we could try it out.

@sam-ln
Copy link
Author

sam-ln commented Jan 31, 2024

I think this only applies for [SecurityCritical] methods. The article I linked says

Transparent code cannot use reflection to access security-critical members, even if the code is fully trusted.

I'm not entirely sure what "transparent" refers to in this context, but I think that this might be the issue.

The .NET environment in Unity (running on Mono) seems to be affected by this restriction, but I'd be surprised if this problem is only Unity-specific.

@sam-ln
Copy link
Author

sam-ln commented Jan 31, 2024

@Miista Sure, I could set up a unity project with your test suite in it if that would help. I guess the easiest way to check for that restriction would be to execute

var methods = typeof(DynamicMethod).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);

If I run it in a .NET Core solution it shows all methods including GetMethodDescriptor, but if I run it in the Unity environment
it only returns the private/internal methods which are not labelled as [SecurityCritical]

@Miista
Copy link
Owner

Miista commented Jan 31, 2024

@sam-ln What should we do if the method does not exist (as is the case for Unity)? Do we provide a better exception message?

Scripting restrictions for Unity: https://docs.unity3d.com/Manual/ScriptingRestrictions.html

I'm reading through the above link and I spot this:

Unity supports reflection on AOT platforms.

On the same page I can see that Mono does not support ahead-of-time. Could this be the reason why it fails?

That said, I can see that GetMethodDescriptor is missing in the Mono implementation.

Mono source: https://github.com/mono/mono/blob/main/mcs/class/corlib/System.Reflection.Emit/DynamicMethod.cs

The method does exist in the reference source: https://github.com/mono/mono/blob/38b0227c1ce0c53058a5d78d080923435132773a/mcs/class/referencesource/mscorlib/system/reflection/emit/dynamicmethod.cs#L580

We could consider copying the implementation verbatim and provide it ourselves if we find that there is none provided by the platform. Although I'm not really keen on writing this ourselves.

@sam-ln
Copy link
Author

sam-ln commented Jan 31, 2024

@Miista Mono is not used for the AOT platforms (They use IL2CCP for that), so that shouldn't be the issue.
It is odd that the function doesn't exist in the Mono implementation, although I can see it in the Rider/ReShaper disassembly(?)
Yea, it would be wonderful if we found a workaround for those cases! Still would be great to figure out what exactly the problem is (missing mono implementation? insufficient trust level?, security policy violation?), so we can give a proper Exception as well.

@Miista
Copy link
Owner

Miista commented Jan 31, 2024

@sam-ln Have you had a look at managed code stripping? Link: https://docs.unity3d.com/Manual/ManagedCodeStripping.html
It's referenced from the scripting restrictions as "if [the] compiler can’t infer that the code is used via reflection the code might not exist at runtime."

It looks like it can be disabled by declaring a Link.xml. Ref: https://docs.unity3d.com/Manual/ManagedCodeStripping.html#LinkXMLAnnotation

I'll be honest with you. I haven't worked with Unity before.

@Miista
Copy link
Owner

Miista commented Feb 21, 2024

@sam-ln Have you had a chance to look more into this?

@sam-ln
Copy link
Author

sam-ln commented Feb 23, 2024

@Miista Good idea regarding the code stripping, this wasn't the issue though (it's disabled in my project).

@sam-ln
Copy link
Author

sam-ln commented Feb 24, 2024

@Miista

I'll be honest with you. I haven't worked with Unity before.

No worries! If you'd like to look into this more, I've set up a demo project for you with Poser imported and some Unit test cases which highlight the issue. Let me know if you need any assistance!
https://github.com/sam-ln/poser-unity-issue

Right now, my best guess is that the problem is the missing implementation of GetMethodDescriptor in Mono (what you mentioned earlier)

@Miista
Copy link
Owner

Miista commented Jun 29, 2024

@sam-ln Did you find a solution or a suitable workaround at least?

@sam-ln
Copy link
Author

sam-ln commented Jul 1, 2024

@Miista Unfortunately not, but I haven't tried since. I think where we left off is that we could write a GetMethodDescriptor replacement for it to work in Mono. Have you had a chance to open it up in Unity and review the problem yourself?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question/investigation Further information or investigation is required
Projects
None yet
Development

No branches or pull requests

2 participants