简体   繁体   中英

How can I walk the method call to an Action, Func, or Delegate in introspector for a custom rule?

I'm writing a custom rule to verify the constructor of any control type calls initialize component.

But when I hit these 2 edge cases:

public Form1(int? testInt,bool testBool,bool testBool2)
        : this(false)
    {
        Action init = ( ) => InitializeComponent( );
        init();
    }
    public Form1(int? testInt, bool testBool, bool? testBool2)
        : this(false)
    {
        Action init = InitializeComponent;
        init( );
    }

I can't seem to walk the init call to see that initializeComponent is getting called in these constructors. I know this is an edge case, and unlikely to happen, but I want to learn how to do it.

The reflector IL looks like this:

.method public hidebysig specialname rtspecialname instance void .ctor(valuetype [mscorlib]System.Nullable`1<int32> testInt, bool testBool, bool testBool2) cil managed
{
.maxstack 3
.locals init (
    [0] class [mscorlib]System.Action init,
    [1] class [mscorlib]System.Action CS$<>9__CachedAnonymousMethodDelegateb)
L_0000: ldnull 
L_0001: stloc.1 
L_0002: ldarg.0 
L_0003: ldc.i4.0 
L_0004: call instance void TestLibrary.Form1::.ctor(bool)
L_0009: nop 
L_000a: nop 
L_000b: ldloc.1 
L_000c: brtrue.s L_001d
L_000e: ldarg.0 
L_000f: ldftn instance void TestLibrary.Form1::<.ctor>b__a()
L_0015: newobj instance void [mscorlib]System.Action::.ctor(object, native int)
L_001a: stloc.1 
L_001b: br.s L_001d
L_001d: ldloc.1 
L_001e: stloc.0 
L_001f: ldloc.0 
L_0020: callvirt instance void [mscorlib]System.Action::Invoke()
L_0025: nop 
L_0026: nop 
L_0027: ret 
}


.method public hidebysig specialname rtspecialname instance void .ctor(valuetype [mscorlib]System.Nullable`1<int32> testInt, bool testBool, valuetype [mscorlib]System.Nullable`1<bool> testBool2) cil managed
{
.maxstack 3
.locals init (
    [0] class [mscorlib]System.Action init)
L_0000: ldarg.0 
L_0001: ldc.i4.0 
L_0002: call instance void TestLibrary.Form1::.ctor(bool)
L_0007: nop 
L_0008: nop 
L_0009: ldarg.0 
L_000a: ldftn instance void TestLibrary.Form1::InitializeComponent()
L_0010: newobj instance void [mscorlib]System.Action::.ctor(object, native int)
L_0015: stloc.0 
L_0016: ldloc.0 
L_0017: callvirt instance void [mscorlib]System.Action::Invoke()
L_001c: nop 
L_001d: nop 
L_001e: ret 
}

I'm walking the constructors as such:

        private Dictionary<Method, bool> _methodContainsInitCall;
public void VisitConstructorsRecursive(Method method, Method initializer)
    {
        Debug.Assert(_methodContainsInitCall.ContainsKey(method));
        var toVisit = new List<Method>( );
        foreach (var instruction in method.Instructions.Where(x => x.OpCode==OpCode.Call || x.OpCode== OpCode.Callvirt))
        {

            if (instruction.Value is Method)
            {
                //&&((Method)instruction.Value).FullName.Contains(".#ctor")
                var callMethod =(Method)instruction.Value;
                if (callMethod.FullName.StartsWith("System.Windows.Forms.Form.#ctor"))
                    continue;
                if (callMethod.IsStatic==false) //can not call instance method InitializeComponent in static method
                {
                    toVisit.Add(callMethod);
                }

                //
                //TestLibrary.Form1.#ctor(System.String)
            }
            if (instruction.Value is Method&&((Method)instruction.Value).FullName.EndsWith(".InitializeComponent"))
            {
                if (_constructorFoundInitializeCall.ContainsKey(method))
                    _constructorFoundInitializeCall[method]=true;
                _methodContainsInitCall[method]=true;
                return;
            }

        }
        foreach (var methodCall in toVisit)
        {
            if (_methodContainsInitCall.ContainsKey(methodCall))
            {
                if (_methodContainsInitCall[methodCall])
                {
                    _constructorFoundInitializeCall[initializer]=true;
                    return;
                }
            }
            else
            {
                _methodContainsInitCall.Add(methodCall, false);
                VisitConstructorsRecursive(methodCall, initializer);
            }


        }
    }

when the virtual call to Action.Invoke() happens, it is flagged as virtual and method.Instructions.Count ==0 as well as method.Body.Count==0

so where is my instruction to call the initialize component hiding that I can verify it is actually being called?

When walking the blocks that make up the constructor, notice the CS$<>9__CachedAnonymousMethodDelegateb typed variable. That class actually contains the compiled delegate that calls InitializeComponent.

If you want to check this, check the locals of the method, find visit the compiler generated type and you will find that it only has one method. Visit that method to check whether it calls Initialize Component.

The problem is that the only way to detect these types of classes is by name. And the C# and VB.NET compilers use a different naming scheme to store these anonymous delegates.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM