Expression<TDelegate>.Compile in Medium Trust environment
Asked Answered
N

1

9

When trying to compile an Expression in a medium trust web app I'm getting a MethodAccessException. Does anyone know of another way to compile an expression under medium trust or a workaround to avoid this exception?

The code that throws the exception:

Expression<Func<object>> efn = 
  Expression.Lambda<Func<object>>(Expression.Convert((plan,typeof(object)));

Func<object> fn = efn.Compile(); // Exception thrown here

The variable plan is an Expression that represents the following execution plan:

{
  Convert(Query(MyProjectNamespace.MyDatabaseTableObject).Provider).Execute
  (
    new QueryCommand(
    "SELECT [t0].[LinkId], [t0].[Url] FROM [dbo].[MyDatabaseTable] AS t0",
    value(System.String[]), 
    r0 => new MyDatabaseTableObject() 
    {
      Id = IIF(r0.IsDBNull(0), 0, 
        Convert(ChangeType(r0.GetValue(0), System.Int32))), 
      Url = IIF(r0.IsDBNull(1), null, 
        Convert(ChangeType(r0.GetValue(1), System.String)))
    }, 
    value(System.Collections.Generic.List[System.String])), 
    new [] {}
  )
}

The full stack trace:

at System.Reflection.MethodBase.PerformSecurityCheck(Object obj, RuntimeMethodHandle method, IntPtr parent, UInt32 invocationFlags)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
at System.Linq.Expressions.ExpressionCompiler.AddGlobal(Type type, Object value)
at System.Linq.Expressions.ExpressionCompiler.GenerateConstant(ILGenerator gen, Type type, Object value, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateConstant(ILGenerator gen, ConstantExpression c, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateArgs(ILGenerator gen, ParameterInfo[] pis, ReadOnlyCollection`1 args)
at System.Linq.Expressions.ExpressionCompiler.GenerateMethodCall(ILGenerator gen, MethodInfo mi, ReadOnlyCollection`1 args, Type objectType)
at System.Linq.Expressions.ExpressionCompiler.GenerateMethodCall(ILGenerator gen, MethodCallExpression mc, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateConvert(ILGenerator gen, UnaryExpression u)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateConditional(ILGenerator gen, ConditionalExpression b)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateMemberAssignment(ILGenerator gen, MemberAssignment binding, Type objectType)
at System.Linq.Expressions.ExpressionCompiler.GenerateBinding(ILGenerator gen, MemberBinding binding, Type objectType)
at System.Linq.Expressions.ExpressionCompiler.GenerateMemberInit(ILGenerator gen, ReadOnlyCollection`1 bindings, Boolean keepOnStack, Type objectType)
at System.Linq.Expressions.ExpressionCompiler.GenerateMemberInit(ILGenerator gen, MemberInitExpression init)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateLambda(LambdaExpression lambda)
at System.Linq.Expressions.ExpressionCompiler.GenerateCreateDelegate(ILGenerator gen, LambdaExpression lambda)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateArgs(ILGenerator gen, ParameterInfo[] pis, ReadOnlyCollection`1 args)
at System.Linq.Expressions.ExpressionCompiler.GenerateNew(ILGenerator gen, NewExpression nex, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateArgs(ILGenerator gen, ParameterInfo[] pis, ReadOnlyCollection`1 args)
at System.Linq.Expressions.ExpressionCompiler.GenerateMethodCall(ILGenerator gen, MethodInfo mi, ReadOnlyCollection`1 args, Type objectType)
at System.Linq.Expressions.ExpressionCompiler.GenerateMethodCall(ILGenerator gen, MethodCallExpression mc, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateConvert(ILGenerator gen, UnaryExpression u)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateLambda(LambdaExpression lambda)
at System.Linq.Expressions.ExpressionCompiler.CompileDynamicLambda(LambdaExpression lambda)
at System.Linq.Expressions.Expression`1.Compile()
at SubSonic.Linq.Structure.DbQueryProvider.Execute(Expression expression)
at SubSonic.Linq.Structure.QueryProvider.System.Linq.IQueryProvider.Execute(Expression expression)
at SubSonic.Linq.Structure.Query`1.GetEnumerator()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at WebApplication1._Default.Page_Load(Object sender, EventArgs e)
at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e)
at System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e)
at System.Web.UI.Control.OnLoad(EventArgs e)
at System.Web.UI.Control.LoadRecursive()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
Niemi answered 7/7, 2010 at 12:34 Comment(6)
You do not have the ReflectionPermission(ReflectionPermissionFlag.MemberAccess) to access non-public members.Cockoftherock
@Jaroslav - I know that's the cause of the error I'm trying to work out what it is that's requiring that permission and how to get round it, if that's possible.Niemi
@Adam: if you use any non-public members (properties, fields, methods, constructors), try it without them (if you can). Even external parameters can cause such behaviour. Try to "reflect" around the used classes...Cockoftherock
@Jaroslav - whilst technically correct - it's actually got nothing to do with the direct use of any non-visible methods; it's a subtle issue to do with baking Type instances into expressions - RuntimeType (used for all reflected type instances at runtime) is internal, and it's causing StrongBox<T> to blow.Peseta
@Andras: Sounds likely. A quick look at the stack trace suggest it's this priece of code ChangeType(r0.GetValue(0), System.Int32). Maybe using an alternative method like ChangeTypeTo<T> could solve the problem - it depends on the implementation of SubSonic though.Cockoftherock
@Jaroslav: Using a generic is a good idea. I found another solution (in my answer) where instead of calling Expression.Constant([type instance]), you can call Expression.Constant([type instance], typeof(Type)) so that the code-generation functions reflect over the reflection-friendly Type instead of RuntimeType.Peseta
P
15

The underlying problem here is that the type being passed to System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes) is either not public, or has a constructor that is not public.

Now - given the simplicity of your code example versus the depth of the stacktrace I believe the problem lies not in plan, but in an expression within plan (since you say in your comment on Marc's answer that it is also an expression) that references the type which is then restricted.

The expression that is the source of the error here is a ConstantExpression which must be of the restricted type.

The only confusing thing about this, however, is that the type argument that AddGlobal passes to Activator.CreateInstance is StrongBox<T>, which is public and has a public constructor - which would imply that this error should be impossible.

Perhaps, however, there is something hidden associated with StrongBox<T> that we can't see through Reflector.

So, I would be looking at the whole expression tree represented by plan and examining all the types referenced in ConstantExpressions to ensure they are all accessible. If after doing that all types are shown to be accessible, this error still occurs then it could be a bug in the framework.

However - I would have thought that such a bug would've been found already for something as simple as a ConstantExpression!

EDIT (Replacing Previous Edit) WITH THE ANSWER

I've got it, and it's a very subtle problem. You can reproduce with this little bit of code in an aspx page that is configured to run in medium trust:

Type t = typeof([any type you fancy]);
Expression expr = Expression.Constant(t);
var lambda = Expression.Lambda<Func<Type>>(expr);
var del = lambda.Compile();
Response.Write(del().ToString());

So, in the code you've provided, it's the expression representing the second argument to ChangeType (took me a while to realise that that is a Sub Sonic method), which appears to be a Type (can't see the code but I think it's a reasonable guess!).

It's baked in the expression as a ConstantExpression of a Type instance. Don't ask how I narrowed down the parameter - lots of stack crawling and reflector work ;)

As mentioned in the first half of my answer, it's difficult to see how the code that the Expression Tree compiler uses can ever create a MethodAccessException, since it's always accessing the public ctor of the StrongBox<T> type.

However, it would get upset if the type passed in as the generic is not public. "But wait," you say, "Type is public!".

That might be, but the Type instance returned at runtime from typeof() or GetType() isn't - it's an instance of RuntimeType - which is internal.

Which is also why the above code snippet will also trigger the same error.

The fix

Change the code that produces the Type argument for ChangeType(,) from

Expression.Constant([type])

(which I'll almost guarantee that it is at the moment) to

Expression.Constant([type], typeof(Type))

This works, because you're explicitly telling the compiler to use the public Type for the constant, instead of the reflected type of RuntimeType.

You can test this fix by applying it to my example code in the previous block and re-running.

Peseta answered 7/7, 2010 at 13:34 Comment(12)
Thanks this is really helpful, do you have any suggestion on a quick way to track down the non accessible types? I'm pretty confident this isn't a framework bug but I'm finding pinpointing the exact cause a significant challenge.Niemi
Hmmm... well I considered how you could do this. Perhaps you can use an expression visitor which looks for ConstantExpression whose type is either not public or which has at least one non-public constructor-that should narrow it down. MSDN has a link to an example expression visitor. I'll take a look and post it up on the answer.Peseta
@Adam - I have updated my answer with some code that might be able to help you diagnose, alongside that link from the MSDN which you'll need for an Expression Tree Visitor. It's not perfect - but it should set you on your way pretty well I hope :)Peseta
@Andras - this is awesome thanks, I'll let you know how I get on :)Niemi
Hmmm... The only two dodgyConstants returned are System.Int32 and System.StringNiemi
@Adam: oh dear, this is a little strange... The stack trace doesn't lie, so therefore perhaps this is something bigger. There are some issues with generating code in medium trust (the RestrictedMemberAccess permission mentioned by the first commenter on your question) - but the MSDN documentation on Expression Trees doesn't mention requiring anything special for to actually do anything. You could try requesting the RestrictedMemberAccess permission - I just can't see any reason why you actually need it...Peseta
Yep, that's what I'm trying to get to the bottom of. I know I can add that permission and skip the problem but I'm trying to fix an issue in SubSonic (subsonicproject.com) so it can be used in medium trust. Debugging the issue is proving problematic to say the least though hence the question. Thanks for your help so far :)Niemi
I reckon we can reproduce this overall issue in a test environment... I'll have a poke about and see what I can find.Peseta
@Adam - I've figured it out (I hope). Please see my updated answer - it's a real keeper of a problem!Peseta
@Andras - Honestly can't thank you enough for this. Amazing detective work you're absolutely right. Thanks again :)Niemi
@Adam - not a problem - a fascinating bug and glad to be of help. Good luck with your work on Subsonic :)Peseta
It's a damn shame this is an obscure problem, because an answer of this quality deserves more up-votes.Conchology

© 2022 - 2024 — McMap. All rights reserved.