Seperating pre-and post-base or chained constructor invocation statements for c # constructor
In order for the project to add mixins to C # using code weaving, I clone the code from the source type's mixin's parameterless instance constructor for the constructors on the target's type. To do this, I divide the constructor into three conceptual parts, and about this I ask for help.
Here are three parts:
- Field initialization, which is performed before calling the base or chained constructor.
- Basic or chained constructor invocation, including loading arguments onto the stack.
- The actual constructor code, compiled from the source code written in the constructor body.
The basic idea is to multiplex the source constructor into these parts. The mux step also included checking local variables (stloc * and ldloc *), so it is important that the command separation is correct. Those target constructors that are called in the base constructors are the targets for cloning the code. Each of these will have the source section 1 cloned into its section 1, and will have a method call added to its section 3 that will invoke a new method containing the source 3 constructor section code in the target type. (It is put into its own method primarily because of the possibility of multiple exit points.)
I've read the constructor section of the C # spec instances, but other than confirming the intentional existence of the three sections that I see, I don't find it helpful. I've had some promising false starts on this one, and instead of trying another bad strategy that passes my test cases and then chokes as soon as it hits something I didn't think about, I hope I can get some the best input from someone with the best experience.
My current "next" thought is to loop through the instructions that ldarg.0 looks for and then find the next method call. If the next method call is a base or chained constructor, I can call this section 2 with the instructions described above as in section 1 and instructions after section 3. However, I am concerned that the instructions may not always have such a clean separation, and I not sure how I could be sure of this.
Another thought is that, since the specification specifically states that variable initialization instructions before a basic or chained constructor call, it may be more reliable to look for the end of instructions setting local fields. Unfortunately, I'm not sure what would be the best way to do this.
Here is an example of the target type and conceptual partitioning I'm looking for for 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 use Mono.Cecil for all my reading and writing. The Bix.Mixers project code can be found at https://github.com/rileywhite/Bix.Mixers.Fody if you're interested. The specific file this question pertains to is at https://github.com/rileywhite/Bix.Mixers.Fody/blob/master/src/Bix.Mixers/Fody/ILCloning/ConstructorMultiplexer.cs .
source to share
The strategy that seems to work is to list the constructor instructions, grouping them as follows:
- If the instruction is not ldarg.0, it is put into a group by itself.
- If the instruction is ldarg.0, then all of the following instructions are grouped together with it until one of the following conditions is met:
- There are call commands, virtcall or calli.
- An instruction is the last instruction before another ldarg.0.
- All instructions are listed.
Once the group has been identified, the last command in the group is checked. If it is a call instruction and the operand is a base or chained constructor, then the group is identified as section 2, which means that the previous instructions are section 1 and the remaining instructions are section 3.
Here is the code that, given the index to start the search, identifies a group of instructions based on these rules.
public static bool TryGetNext(IList<Instruction> sourceInstructions, int firstIndex, out InstructionGroup instructionGroup)
{
Contract.Requires(sourceInstructions != null);
if (firstIndex < 0 || firstIndex >= sourceInstructions.Count)
{
instructionGroup = null;
return false;
}
var instructions = new List<Instruction>();
var instruction = sourceInstructions[firstIndex];
instructions.Add(instruction);
int lastIndex;
if (instruction.OpCode.Code != Code.Ldarg_0) { lastIndex = firstIndex; }
else
{
int i;
// calls into base and chained constructors start like this
// so we'll look for the next call instruction or any instruction where the next instruction is another ldarg.0
// meaning that the stack was cleared at some point
// there is no assumption that this grouping is generally useful, but the hope is that it will catch constructor calls in this specific case
var isLastInstructionFound = false;
for (i = firstIndex + 1; !isLastInstructionFound && i < sourceInstructions.Count; i++)
{
instruction = sourceInstructions[i];
instructions.Add(instruction);
if (instruction.OpCode.Code == Code.Call ||
instruction.OpCode.Code == Code.Callvirt ||
instruction.OpCode.Code == Code.Calli ||
(instruction.Next != null && instruction.Next.OpCode.Code == Code.Ldarg_0))
{
isLastInstructionFound = true;
}
}
lastIndex = i - 1;
}
instructionGroup = new InstructionGroup(firstIndex, lastIndex, instructions);
return true;
}
If you're interested, you can see the full code at https://github.com/rileywhite/Bix.Mixers.Fody/blob/0.1.7/src/Bix.Mixers/Fody/ILCloning/ConstructorMultiplexer.cs .
Even though this seems to work, I won't pick this as the definitive answer because it's another heuristic. I would like to get a real answer from someone with more experience.
source to share