Unexpected behaviour of MetadataDeepCopier

Topics: Metadata Model
Nov 29, 2012 at 2:20 PM
Edited Nov 29, 2012 at 2:57 PM

[UPDATE: I assumed that I had to use the MetadataDeepCopier to get access to a mutable representation due to this post: http://ccimetadata.codeplex.com/wikipage?title=Make%20a%20Mutable%20Copy%20of%20an%20Immutable%20Representation. Now I know that there already is a mutable representation available in the scenario below - I only have to do a cast:

  protected override void EmitMethodBody(IMethodBody methodBody) { 
    var mutableMethodBody = (MethodBody) methodBody;

However - the behaviour below is still unexpected in my opinion]


using a custom ILRewriter I want to create a mutable copy of a immutable representation of a method body (to be able to adjust MaxStack). Therefore I use a MetadataDeepCopier. Now my code shows some unexpected behaviour: using the immutable representation the Value of a ldfld Operation is of type FieldDefinition, using the mutable representation the type is FieldReference.

Is this expected? I assumed that MetadataDeepCopier returns an exact copy. Which of the types is expected? Both? Are there other types as well? Is there a table that lists the expected types of the Value property for all OperationCodes?




class MyIlRewriter : ILRewriter { 
  MetadataDeepCopier copier; 
  public MyIlRewriter(...) { 
    // ...
    copier = new MetadataDeepCopier(host);
  protected override void EmitMethodBody(IMethodBody methodBody) { 
    // base.EmitMethodBody(copier.Copy(methodBody));
  protected override void EmitOperation(Operation operation) {
    switch(operation.OperationCode) { 
      case OperationCode.Ldfld: 
        var fieldDef = operation.Value as FieldDefinition; 
        // var fieldRef = operation.Value as FieldReference; 
Nov 29, 2012 at 3:44 PM

Hi Jochen

This behavior is clearly not expected by everybody, but it is the way things are intended to be. The deep copier will copy everything in the spanning tree starting with the root it is given and turn all references to objects outside of the tree into true references. This makes it easier to graft copies into other places.

In your scenario, one typically starts with a deep copy of the entire assembly, in which case the copy is identical to the original.


Nov 29, 2012 at 6:21 PM

You are right - I already made a deep copy of the entire assembly... thanks for the clarification! One question is still remaining: is there a table that maps the OperationCode of an Operation to the expected type of its Value property?

Nov 30, 2012 at 4:51 AM

Not in the documentation on this site. (Feel free to correct this. ;-)

The easiest place to look is in the source code. See the body of SerializeMethodBodyIL in PeWriter.cs in the PeWriter project.


Nov 30, 2012 at 11:20 AM

Thanks again!

I wrote some code to create such a table - Im going to attatch it below because I don't know where it would be expected in the documentation (maybe in the Operation class?)


        public static String GenerateTable() {
            StringBuilder table = new StringBuilder();
            table.AppendLine("Given an IOperation operation this table maps operation.OperationCode to the expected Type of operation.Value");
            table.AppendLine("operation.OperationCode       | operation.Value.GetType()");
            foreach(OperationCode opCode in Enum.GetValues(typeof(OperationCode)).Cast<OperationCode>()) {
                String space = new String(' ', 30-opCode.ToString().Length);
                table.AppendLine(opCode.ToString() + space + "| " + ExpectedTypeOfValue(opCode));
            return table.ToString();

        // This method is an adaption of Metadata/PeWriter/PeWriter.cs, SerializeMethodBodyIl, lines 3508-3660
        // Given an IOperation operation and opCode = operation.OperationCode the method returns the expected Type of operation.Value
        public static String ExpectedTypeOfValue(OperationCode opCode) {
                switch (opCode) {
                    case OperationCode.Beq:
                    case OperationCode.Bge:
                    case OperationCode.Bge_Un:
                    case OperationCode.Bgt:
                    case OperationCode.Bgt_Un:
                    case OperationCode.Ble:
                    case OperationCode.Ble_Un:
                    case OperationCode.Blt:
                    case OperationCode.Blt_Un:
                    case OperationCode.Bne_Un:
                    case OperationCode.Br:
                    case OperationCode.Brfalse:
                    case OperationCode.Brtrue:
                    case OperationCode.Leave:
                        //^ assume operation.Value is uint;
                        return "uint";
                    case OperationCode.Beq_S:
                    case OperationCode.Bge_S:
                    case OperationCode.Bge_Un_S:
                    case OperationCode.Bgt_S:
                    case OperationCode.Bgt_Un_S:
                    case OperationCode.Ble_S:
                    case OperationCode.Ble_Un_S:
                    case OperationCode.Blt_S:
                    case OperationCode.Blt_Un_S:
                    case OperationCode.Bne_Un_S:
                    case OperationCode.Br_S:
                    case OperationCode.Brfalse_S:
                    case OperationCode.Brtrue_S:
                    case OperationCode.Leave_S:
                        //^ assume operation.Value is uint;
                        return "uint";
                    case OperationCode.Box:
                    case OperationCode.Castclass:
                    case OperationCode.Constrained_:
                    case OperationCode.Cpobj:
                    case OperationCode.Initobj:
                    case OperationCode.Isinst:
                    case OperationCode.Ldelem:
                    case OperationCode.Ldelema:
                    case OperationCode.Ldobj:
                    case OperationCode.Mkrefany:
                    case OperationCode.Refanyval:
                    case OperationCode.Sizeof:
                    case OperationCode.Stelem:
                    case OperationCode.Stobj:
                    case OperationCode.Unbox:
                    case OperationCode.Unbox_Any:
                        //^ assume operation.Value is ITypeReference;
                        return "ITypeReference";
                    case OperationCode.Call:
                    case OperationCode.Callvirt:
                    case OperationCode.Jmp:
                    case OperationCode.Ldftn:
                    case OperationCode.Ldvirtftn:
                    case OperationCode.Newobj:
                        //^ assume operation.Value is IMethodReference;
                        return "IMethodReference";
                    case OperationCode.Calli:
                        //^ assume operation.Value is IFunctionPointerTypeReference;
                        return "IFunctionPointerTypeReference";
                    case OperationCode.Ldarg:
                    case OperationCode.Ldarga:
                    case OperationCode.Starg:
                        //if (operation.Value == null) 
                        //    //writer.WriteUshort(0);
                        //else {
                        //    //^ assume operation.Value is IParameterDefinition;
                        //    //writer.WriteUshort(GetParameterIndex((IParameterDefinition)operation.Value));
                        return "null or IParameterDefinition";
                    case OperationCode.Ldarg_S:
                    case OperationCode.Ldarga_S:
                    case OperationCode.Starg_S:
                        //if (operation.Value == null)
                        //    writer.WriteByte(0);
                        //else {
                        //    //^ assume operation.Value is IParameterDefinition;
                        //    writer.WriteByte((byte)GetParameterIndex((IParameterDefinition)operation.Value));
                        return "null or IParameterDefinition";
                    case OperationCode.Ldc_I4:
                        //^ assume operation.Value is int;
                        return "int";
                    case OperationCode.Ldc_I4_S:
                        //^ assume operation.Value is int;
                        return "int";
                    case OperationCode.Ldc_I8:
                        //^ assume operation.Value is long;
                        return "long";
                    case OperationCode.Ldc_R4:
                        //^ assume operation.Value is float;
                        return "float";
                    case OperationCode.Ldc_R8:
                        //^ assume operation.Value is double;
                        return "double";
                    case OperationCode.Ldfld:
                    case OperationCode.Ldflda:
                    case OperationCode.Ldsfld:
                    case OperationCode.Ldsflda:
                    case OperationCode.Stfld:
                    case OperationCode.Stsfld:
                        //^ assume operation.Value is IFieldReference;
                        return "IFieldReference";
                    case OperationCode.Ldloc:
                    case OperationCode.Ldloca:
                    case OperationCode.Stloc:
                        //^ assume operation.Value is ILocalDefinition;
                        return "ILocalDefinition";
                    case OperationCode.Ldloc_S:
                    case OperationCode.Ldloca_S:
                    case OperationCode.Stloc_S:
                        //^ assume operation.Value is ILocalDefinition;
                        return "ILocalDefinition";
                    case OperationCode.Ldstr:
                        //^ assume operation.Value is string;
                        return "string";
                    case OperationCode.Ldtoken:
                        //uint token = 0;
                        //IFieldReference/*?*/ fieldRef = operation.Value as IFieldReference;
                        //if (fieldRef != null)
                        //    token = this.GetFieldToken(fieldRef);
                        //else {
                        //    IMethodReference/*?*/ methodRef = operation.Value as IMethodReference;
                        //    if (methodRef != null)
                        //        token = this.GetMethodToken(methodRef);
                        //    else {
                        //        //^ assume operation.Value is ITypeReference;
                        //        token = this.GetTypeToken((ITypeReference)operation.Value);
                        //    }
                        return "IFieldReference or IMethodReference or ITypeReference";
                    case OperationCode.Newarr:
                        //^ assume operation.Value is IArrayTypeReference;
                        return "IArrayTypeReference";
                    case OperationCode.No_:
                        //^ assume operation.Value is OperationCheckFlags;
                        return "OperationCheckFlags";
                    case OperationCode.Switch:
                        //^ assume operation.Value is uint[];
                        return "uint[]";
                    case OperationCode.Unaligned_:
                        //^ assume operation.Value is byte;
                        return "byte";
                        return "";