For a project to add mixins to C# using code weaving, I am cloning code from a source mixin type's parameterless instance constructor to constructors in a target type. For the purposes of this, I divide a constructor into three conceptual parts, and this is what I am asking for help with.
Here are the three parts:
- Field initialization that runs before the base or chained constructor call.
- Base or chained constructor call, including loading of arguments onto the stack.
- Actual constructor code compiled from source code written in the constructor body.
The basic idea is to multiplex the source constructor into these pieces. The multiplexing step also involved checking local variables (stloc* and ldloc*), so it's important that the instruction separation is correct. Those target constructors that call into base constructors are the code cloning targets. Each one will have the source's section 1 cloned into its section 1 and will have a method call added to its section 3 which will invoke a new method that contains the source constructor's section 3 code within the target type. (It's put into its own method primarily because of the possibility of multiple exit points.)
I've read through the C# spec's instance constructor section, but other than confirming the intentional existence of the 3 sections that I'm seeing, I don't find it helpful. I've had a couple of promising false starts on this, and rather than try yet another bad strategy that passes my test cases and then chokes as soon as it hits something I didn't think of, I'm hoping that I can get some better input from somebody with better experience.
My current "next" thought is to cycle through instructions looking for ldarg.0, and then to detect the next method call. If that next method call is a base or chained constructor, then I can call this Section 2, with instructions before as Section 1 and instructions after as Section 3. I'm concerned, though, that the instructions may not always have such a clean separation, and I'm not sure how I could be certain of such a thing.
Another thought is that because the spec specifically states that variable initialization instructions come before the base or chained constructor call, it might be more reliable to look for the end of instructions that set local fields. Unfortunately, I'm not certain what would be the best way to go about that.
Here's an example of a target type and the conceptual breakdown that I'm looking for of the constructors.
public class MultipleConstructorsTarget : MultipleConstructorsTargetBase
{
public MultipleConstructorsTarget()
{
var values = Tuple.Create(783535, "KNion wineofn oianweiof nqiognui ndf", new UriBuilder { Host = "j.k.l" });
this.OriginalUninitializedInt = values.Item1;
this.OriginalUninitializedString = values.Item2;
this.OriginalUninitializedObject = values.Item3;
}
public MultipleConstructorsTarget(int i) : this(i, "A iuohiogfniouhe uihui iu.", new UriBuilder { Host = "g.h.i" }) { }
public MultipleConstructorsTarget(int i, string j) : this(i, j, new UriBuilder { Host = "d.e.f" }) { }
public MultipleConstructorsTarget(int i, string j, UriBuilder k)
: base(i)
{
this.OriginalUninitializedInt = i;
this.OriginalUninitializedString = j;
this.OriginalUninitializedObject = k;
}
public int OriginalInitializedInt = 48685;
public string OriginalInitializedString = "Tion3lao ehiuawh iuh buib ld";
public UriBuilder OriginalInitializedObject = new UriBuilder { Host = "a.b.c" };
public int OriginalUninitializedInt;
public string OriginalUninitializedString;
public UriBuilder OriginalUninitializedObject;
}
For MultipleConstructorsTarget()
Section 1
IL_0000: ldarg.0
IL_0001: ldc.i4 0xbe2d
IL_0006: stfld int32 Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::OriginalInitializedInt
IL_000b: ldarg.0
IL_000c: ldstr "Tion3lao ehiuawh iuh buib ld"
IL_0011: stfld string Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::OriginalInitializedString
IL_0016: ldarg.0
IL_0017: newobj instance void [System]System.UriBuilder::.ctor()
IL_001c: stloc.2
IL_001d: ldloc.2
IL_001e: ldstr "a.b.c"
IL_0023: callvirt instance void [System]System.UriBuilder::set_Host(string)
IL_0028: ldloc.2
IL_0029: stfld class [System]System.UriBuilder Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::OriginalInitializedObject
Section 2
IL_002e: ldarg.0
IL_002f: call instance void Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTargetBase::.ctor()
Section 3
IL_0034: ldc.i4 0xbf4af
IL_0039: ldstr "KNion wineofn oianweiof nqiognui ndf"
IL_003e: newobj instance void [System]System.UriBuilder::.ctor()
IL_0043: stloc.1
IL_0044: ldloc.1
IL_0045: ldstr "j.k.l"
IL_004a: callvirt instance void [System]System.UriBuilder::set_Host(string)
IL_004f: ldloc.1
IL_0050: call class [mscorlib]System.Tuple`3<!!0,!!1,!!2> [mscorlib]System.Tuple::Create<int32,string,class [System]System.UriBuilder>(!!0, !!1, !!2)
IL_0055: stloc.0
IL_0056: ldarg.0
IL_0057: ldloc.0
IL_0058: callvirt instance !0 class [mscorlib]System.Tuple`3<int32,string,class [System]System.UriBuilder>::get_Item1()
IL_005d: stfld int32 Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::OriginalUninitializedInt
IL_0062: ldarg.0
IL_0063: ldloc.0
IL_0064: callvirt instance !1 class [mscorlib]System.Tuple`3<int32,string,class [System]System.UriBuilder>::get_Item2()
IL_0069: stfld string Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::OriginalUninitializedString
IL_006e: ldarg.0
IL_006f: ldloc.0
IL_0070: callvirt instance !2 class [mscorlib]System.Tuple`3<int32,string,class [System]System.UriBuilder>::get_Item3()
IL_0075: stfld class [System]System.UriBuilder Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::OriginalUninitializedObject
IL_007a: ret
For MultipleConstructorsTarget(int i)
Section 1
(empty)
Section 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: ldstr "A iuohiogfniouhe uihui iu."
IL_0007: newobj instance void [System]System.UriBuilder::.ctor()
IL_000c: stloc.0
IL_000d: ldloc.0
IL_000e: ldstr "g.h.i"
IL_0013: callvirt instance void [System]System.UriBuilder::set_Host(string)
IL_0018: ldloc.0
IL_0019: call instance void Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::.ctor(int32, string, class [System]System.UriBuilder)
Section 3
IL_001e: ret
For MultipleConstructorsTarget(int i, string j)
Section 1
(empty)
Section 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: ldarg.2
IL_0003: newobj instance void [System]System.UriBuilder::.ctor()
IL_0008: stloc.0
IL_0009: ldloc.0
IL_000a: ldstr "d.e.f"
IL_000f: callvirt instance void [System]System.UriBuilder::set_Host(string)
IL_0014: ldloc.0
IL_0015: call instance void Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::.ctor(int32, string, class [System]System.UriBuilder)
Section 3
IL_001a: ret
For MultipleConstructorsTarget(int i, string j, UriBuilder k)
Section 1
IL_0000: ldarg.0
IL_0001: ldc.i4 0xbe2d
IL_0006: stfld int32 Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::OriginalInitializedInt
IL_000b: ldarg.0
IL_000c: ldstr "Tion3lao ehiuawh iuh buib ld"
IL_0011: stfld string Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::OriginalInitializedString
IL_0016: ldarg.0
IL_0017: newobj instance void [System]System.UriBuilder::.ctor()
IL_001c: stloc.0
IL_001d: ldloc.0
IL_001e: ldstr "a.b.c"
IL_0023: callvirt instance void [System]System.UriBuilder::set_Host(string)
IL_0028: ldloc.0
IL_0029: stfld class [System]System.UriBuilder Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::OriginalInitializedObject
Section 2
IL_002e: ldarg.0
IL_002f: ldarg.1
IL_0030: call instance void Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTargetBase::.ctor(int32)
Section 3
IL_0035: ldarg.0
IL_0036: ldarg.1
IL_0037: stfld int32 Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::OriginalUninitializedInt
IL_003c: ldarg.0
IL_003d: ldarg.2
IL_003e: stfld string Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::OriginalUninitializedString
IL_0043: ldarg.0
IL_0044: ldarg.3
IL_0045: stfld class [System]System.UriBuilder Bix.Mixers.Fody.TestMixinTargets.MultipleConstructorsTarget::OriginalUninitializedObject
IL_004a: ret
I'm using Mono.Cecil for all of my IL reading and writing. You can find the Bix.Mixers project code at https://github.com/rileywhite/Bix.Mixers.Fody if you are interested. The specific file that this question is in regards to is at https://github.com/rileywhite/Bix.Mixers.Fody/blob/master/src/Bix.Mixers/Fody/ILCloning/ConstructorMultiplexer.cs.