Type load failure when mutating generic type

May 30, 2010 at 7:32 PM

I'm trying to mutate a type by injecting a method, and rewriting some of the existing methods to call the injected method.  This is working fine for types without generic parameters, but it's causing a type load failure when mutating generic types.

Here's a simplified version of my mutator that is causing the problem:

  public class MyMutator : CodeMutator {
    public MyMutator(IMetadataHost host, ISourceLocationProvider sourceLocationProvider)
      : base(host, sourceLocationProvider) {
    }

    MethodDefinition testMethod;
    protected override void Visit(TypeDefinition typeDefinition) {
      if (typeDefinition.Name.Value != "<Module>") {
        var body = new Microsoft.Cci.MutableCodeModel.SourceMethodBody(this.host, 
          this.sourceLocationProvider, null);
        testMethod = new MethodDefinition {
          Body = body,
          CallingConvention = CallingConvention.HasThis,
          ContainingTypeDefinition = this.GetCurrentType(),
          IsVirtual = false,
          IsStatic = false,
          IsHiddenBySignature = true,
          Name = this.host.NameTable.GetNameFor("Test"),
          Type = this.host.PlatformType.SystemVoid,
          Visibility = TypeMemberVisibility.Private,
          Parameters = new System.Collections.Generic.List<IParameterDefinition>(),
        };
        body.Block = new BlockStatement();
        body.LocalsAreZeroed = true;
        body.MethodDefinition = testMethod;

        typeDefinition.Methods.Add(testMethod);
      }
      base.Visit(typeDefinition);
    }

    public override MethodDefinition Visit(MethodDefinition methodDefinition) {
      var body = methodDefinition.Body as ISourceMethodBody;

      if (body != null && !methodDefinition.IsConstructor && 
          methodDefinition.Name.Value != "Test") {
        var block = body.Block as BlockStatement;

        if (block != null) {
          block.Statements.Insert(0, new ExpressionStatement {
            Expression = new MethodCall {
              IsStaticCall = false,
              IsVirtualCall = false,
              ThisArgument = new ThisReference(),
              MethodToCall = testMethod,
              Type = this.host.PlatformType.SystemVoid,
              Arguments = new System.Collections.Generic.List<IExpression>(),
            },
          });
        }
      }
      return base.Visit(methodDefinition);
    }
  }

Reflector shows that the code being generated looks like this:

    L_0000: ldarg.0 
    L_0001: call instance void Test.Generics`1::Test()

Instead of:

    L_0006: ldarg.0 
    L_0007: call instance void Test.Generics`1<!T><!--T-->::Test()
How do I output a proper method call for generic types?

Coordinator
May 30, 2010 at 9:14 PM

The problem you are running into is the restriction in IL/MD that you cannot refer directly to a generic type (template). Instead, you must always refer to an instance of a generic type.

In this case, it is not totally obvious from the generating code what the problem is, but the ILDasm listing makes it clear enough. Mapping this back to the code, you'll notice that your method call refers to the called method via the value of "testMethod". Where did that come from? Well, you created the method and added it to the generic type (template). The containing type of the method is thus the template, not an instance. To get a method that refers to an instance, you have to instantiate the template and obtain the method from the instance.

Doing this is somewhat tricky, so there are helpers. First of all, a generic type template has a property called InstanceType, which will give you a generic type instance whose type arguments are exactly the type parameters of the generic template. Secondly, TypeHelper has a method that will lookup the corresponding member of the instance type, given "testMethod" as its second argument and the instance type as its first argument.

This is all rather more complicated than I'd like it to be, but I'm trying to stay as close to the actual metadata model in order to make sure that CCI does not lose any necessary details in its abstraction over metadata.

May 30, 2010 at 11:53 PM

Thanks.  That worked.  I figured that there had to be something like that but it wasn't obvious to me what field/method I was looking for.