Wednesday, October 3, 2007

Applying Mono.Cecil solution to the Spring.NET Jira-606

WORK IN PROGRESS

Finding the place for the cut.

Current stack of execution for the type's custom attribute copy is (from the bottom up):

Spring.Core.dll!Spring.Util.ReflectionUtils.CreateCustomAttribute(System.Type type = {Name = "CustomTestAttribute" FullName = "Spring.Wcf.Services.WcfServiceProxyFactoryTests+CustomTestAttribute"}, object[] ctorArgs = {Dimensions:[0]}, System.Attribute sourceAttribute = {Spring.Wcf.Services.WcfServiceProxyFactoryTests.CustomTestAttribute}) Line 688
Spring.Core.dll!Spring.Util.ReflectionUtils.CreateCustomAttribute(System.Type type = {Name = "CustomTestAttribute" FullName = "Spring.Wcf.Services.WcfServiceProxyFactoryTests+CustomTestAttribute"}, System.Attribute sourceAttribute = {Spring.Wcf.Services.WcfServiceProxyFactoryTests.CustomTestAttribute}) Line 772
Spring.Core.dll!Spring.Util.ReflectionUtils.CreateCustomAttribute(System.Attribute sourceAttribute = {Spring.Wcf.Services.WcfServiceProxyFactoryTests.CustomTestAttribute}) Line 788
Spring.Core.dll!Spring.Proxy.AbstractProxyTypeBuilder.ApplyTypeAttributes(System.Reflection.Emit.TypeBuilder typeBuilder = {Name = "CompositionAopProxy_6a4ecf7bdbd8440396704c96dd555e9e" FullName = "CompositionAopProxy_6a4ecf7bdbd8440396704c96dd555e9e"}, System.Type targetType = {Name = "WcfServiceImpl" FullName = "Spring.Wcf.Services.WcfServiceProxyFactoryTests+WcfServiceImpl"}) Line 238
Spring.Aop.dll!Spring.Aop.Framework.DynamicProxy.CompositionAopProxyTypeBuilder.BuildProxyType() Line 84
Spring.Aop.dll!Spring.Aop.Framework.DynamicProxy.CachedAopProxyFactory.BuildProxyType(Spring.Proxy.IProxyTypeBuilder typeBuilder = {Spring.Aop.Framework.DynamicProxy.CompositionAopProxyTypeBuilder}) Line 79

Spring.Aop.dll!Spring.Aop.Framework.DynamicProxy.DefaultAopProxyFactory.CreateAopProxy(Spring.Aop.Framework.AdvisedSupport advisedSupport = {Spring.Aop.Framework.ProxyFactory:

1 interfaces=[[Spring.Wcf.Services.WcfServiceProxyFactoryTests+IWcfService] -> target[Spring.Wcf.Services.WcfServiceProxyFactoryTests+WcfServiceImpl]];

1 pointcuts=[DefaultPointcutAdvisor: pointcut=[TruePointcut.TRUE] advice=[Spring.Aop.Interceptor.NopInterceptor]];

targetSource=[Singleton target source (not dynamic): target=[Spring.Wcf.Services.WcfServiceProxyFactoryTests+WcfServiceImpl]];

advisorChainFactory=Spring.Aop.Framework.HashtableCachingAdvisorChainFactory;

proxyTargetType=False; proxyTargetAttributes=True; exposeProxy=False; isFrozen=False; optimize=False; aopProxyFactory=Spring.Aop.Framework.DynamicProxy.CachedAopProxyFactory; }) Line 91

Spring.Aop.dll!Spring.Aop.Framework.AdvisedSupport.CreateAopProxy() Line 1391
Spring.Aop.dll!Spring.Aop.Framework.ProxyFactory.GetProxy() Line 92
Spring.Wcf.Tests.exe!Spring.Wcf.Services.WcfServiceProxyFactoryTests.TestWcfBasicHttpBindingWithAop() Line 101
Spring.Wcf.Tests.exe!Spring.Program.Main(string[] args = {Dimensions:[0]}) Line 15

Walking the stack of the test execution brings us to the ApplyTypeAttributes method of \Spring.Core\Proxy\AbstractProxyTypeBuilder.cs. Its signature is equivalent to the signature of the method created during the proof of concept work:

CopyTypeAttributes(Type source, TypeBuilder target)

With parameters named in quite an opposite way but meaning the same as those in ApplyTypeAttributes.
The idea was to have a less granular API than the current of the ReflectionUtils, with few reasons behind it.

ApplyTypeAttributes signature and body:

protected virtual void ApplyTypeAttributes(TypeBuilder typeBuilder, Type targetType)
        {
            foreach (object attr in GetTypeAttributes(targetType))
            {
                if (attr is CustomAttributeBuilder)
                {
                    typeBuilder.SetCustomAttribute((CustomAttributeBuilder)attr);
                }
                else if (attr is Attribute)
                {
                    typeBuilder.SetCustomAttribute(
                        ReflectionUtils.CreateCustomAttribute((Attribute) attr));
                }
            }
        }

lends itself to the natural application of a Mono.Cecil proof of concept method created.

Sequence of changes.

  • Creating ReflectionUtils2 class side by side with a ReflectionUtils.
  • Changing the signature of the method to be

CopyTypeAttributes(TypeBuilder typeBuilder, Type targetType)

        So it just matches fully the current convention of the ApplyTypeAttributes method.

  • Adding Mono.Cecil.dll into the C:\DEV\OpenSource\Spring.Net\lib\Net\2.0 folder and creating the reference to it from the Spring.Core project.

Pitfalls.

  • Nested types.

Nested types FullName notation is as: Spring.Wcf.Services.WcfServiceProxyFactoryTests+WcfServiceImpl
Mono.Cecil notation for the same is:
Spring.Wcf.Services.WcfServiceProxyFactoryTests/WcfServiceImpl

For nested types which refer to the nested attributes the Mono.Cecil scope is different than for the regular classes (and namespace value is empty). That is to be communicated with the Mono.Cecil author if he is interested, but workaround is very simple:

if (typeReference.Scope is Mono.Cecil.ModuleReference)
{
    return typeReference.Module.Assembly.Name.ToString();
}
if (typeReference.Scope is Mono.Cecil.AssemblyNameReference)
{
    return typeReference.Scope.ToString();
}
// TODO: Use resource string, but Spring.NET is not using resources... ask Mark/Bruno (SD)
throw new NotImplementedException("GetAssemblyFullName method is not implemented " +
    " to work for the {0} type of scope. Only supported scopes are " +
    "Mono.Cecil.ModuleReference, Mono.Cecil.AssemblyNameReference");

  • System and GAC'ed types

I really expected an issue to happen when copying attributes from the system or GAC'ed types, but there was no problem with that as we fed to Mono.Cecil the location of system or GAC'ed assemblies as:

System.Type - C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorlib.dll
System.Web.Services.WebService - C:\WINDOWS\assembly\GAC_MSIL\System.Web.Services\2.0.0.0__b03f5f7f11d50a3a\System.
Web.Services.dll
  • Security

Though as follows from the above note on GAC'ed and System types there will be security implications for access rights to read those dlls. Normally the service account would have those rights (as those in the Worker Process group) but still, in case when it is impersonated account that is coming and we read assembly in this context it may cause a problem then.
Though the same constraints and conditions apply as in case of ASP.NET apps (or any) for example.

  • Performance

It takes quite a while to Mono.Cecil to read through the System.Web.Services.dll assembly (less then 1 minute but close to it). Mono.Cecil.AssemblyFactory.GetAssembly method parses all of the types inside the assembly. Lets see if there is any way how to limit it only to reading the metadata for type we are interested into...
And I'm quite sorry to Cecil! It turns out that I had tracepoints all over the place onto Cecil's code and that caused quite a delay when it ran. I tried to profile it (no debugger then) and it was just instant, I removed the tracepoints and again it is just instant on the mscorlib even. Me bad! Though as now I can get into the milliseconds copy attributes for System.Type, it shows that there is a problem for the ComDefaultInterface(Type) to be copied.

  • Compiler resolved values

We do use text editor when we create the code, which means that we are pretty limited into what we can put into our custom attributes constructor, as it should be pretty static content. There is a twist though, as compiler can still do some static resolutions!
What happens here is that for the following definition of the Type attributes, the ComDefaultInterfaceAttribute is applied with typeof resolved during the compile time.

I believe that it is the only compiler resolvable entity right now in .NET. It means that it is still static, though question is what are metadata available to us to bring it back to life as Type. Lets step back a bit and add the scenario to the tests!

Ok, added, and as one could expect it is the fully qualified type name, yupiee anyway :).

if (!attribute.Resolved) attribute.Resolve();

To be continued tomorrow....

No comments: