Search code examples
c#.netperformanceexpression-treesdynamic-assemblies

.NET: Accessing non-public members from a dynamic assembly


I'm working on a library that allows users to input arbitrary expressions. My library then compiles those expressions as part of a larger expression into a delegate. Now, for still unknown reasons compiling the expression with Compile sometimes/often results in code that is far slower than it would be if it weren't a compiled expression. I asked a question about this before and one workaround was to not use Compile, but CompileToMethod and create a static method on a new type in a new dynamic assembly. That works and the code is fast.

But users can input arbitrary expressions and it turns out that if the user calls a non-public function or accesses a non-public field in the expression, it throws a System.MethodAccessException (in the case of a non-public method) when the delegate is invoked.

What I could probably do here is create a new ExpressionVisitor that checks if the expression accesses anything non-public and use the slower Compile in those cases, but I'd rather have that the dynamic assembly somehow gets the rights to access the non-public members. Or find out if there's anything I can do about Compile being slower (sometimes).

The full code to reproduce this problem:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

namespace DynamicAssembly
{
  public class Program
  {
    private static int GetValue()
    {
      return 1;
    }

    public static int GetValuePublic()
    {
      return 1;
    }

    public static int Foo;

    static void Main(string[] args)
    {
      Expression<Func<int>> expression = () => 10 + GetValue();

      Foo = expression.Compile()();

      Console.WriteLine("This works, value: " + Foo);

      Expression<Func<int>> expressionPublic = () => 10 + GetValuePublic();

      var compiledDynamicAssemblyPublic = (Func<int>)CompileExpression(expressionPublic);

      Foo = compiledDynamicAssemblyPublic();

      Console.WriteLine("This works too, value: " + Foo);

      var compiledDynamicAssemblyNonPublic = (Func<int>)CompileExpression(expression);

      Console.WriteLine("This crashes");

      Foo = compiledDynamicAssemblyNonPublic();
    }

    static Delegate CompileExpression(LambdaExpression expression)
    {
      var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
        new AssemblyName("MyAssembly"+ Guid.NewGuid().ToString("N")), 
        AssemblyBuilderAccess.Run);

      var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module");

      var typeBuilder = moduleBuilder.DefineType("MyType", TypeAttributes.Public);

      var methodBuilder = typeBuilder.DefineMethod("MyMethod", 
        MethodAttributes.Public | MethodAttributes.Static);

      expression.CompileToMethod(methodBuilder);

      var resultingType = typeBuilder.CreateType();

      var function = Delegate.CreateDelegate(expression.Type, 
        resultingType.GetMethod("MyMethod"));

      return function;
    }
  }
}

Solution

  • The problem is not permissions because there is no permission that can allow you to access a non-public field or member of another class without reflection. This is analogous to the situation where you compiled two non-dynamic assemblies and one assembly calls a public method in the second assembly. Then if you change the method to private without recompiling the first assembly, the first assemblies call will now fail at runtime. In other words the expression in your dynamic assembly is being compiled into an ordinary method call which it doesn't have permission to call anymore than you do from another class even in the same assembly.

    Since no permission can solve your problem, you might be able to transform non-public field and method references into subexpressions that use reflection.

    Here is an example taken from your test case. This fails:

    Expression<Func<int>> expression = () => 10 + GetValue();
    

    but this will succeed:

    Expression<Func<int>> expression = () => 10 + (int)typeof(Program).GetMethod("GetValue", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null);
    

    Since this does not crash with an exception, you can see that your dynamic assembly does have reflection permission and it can access the private method, it just can't do it using an ordinary method call that CompileToMethod results in.