0

Compiler converts LINQ function predicates into FieldInfo with names "Predicate`1". I want to analyze these FieldInfo to see which members, if any, are referenced. For example the predicate below uses the user-defined method "ArchName":

list.FindAll(m => !m.ArchName().Contains("<>c"))

I'm not aware that FieldInfo has any related MethodInfo members. What might be done?

Thanks in advance!

Minho17
  • 44
  • 6
  • Could it be an extension method? – Hans Kesting May 15 '21 at 20:35
  • @HansKesting I prefer not using 3rd party resources. What do you have in mind? – Minho17 May 15 '21 at 20:37
  • Extension method doesn't need to be 3rd party, it could be defined elsewhere in the project. In Visual Studio, put the cursor on that method and hit F12 – Hans Kesting May 15 '21 at 20:48
  • ArchName() is an extension method, yes. – Minho17 May 15 '21 at 20:49
  • You have to parse the methodbody and then resolve the found tokens. Have a look here: https://stackoverflow.com/a/33034906/4035472 – thehennyy May 15 '21 at 21:21
  • @thehennyy that was for a Property (which has a getMethod and a setMethod). This is a Field. Do you know of a way to extract a methodbody from a field? – Minho17 May 15 '21 at 21:43
  • The field should be of a compiler generated type and that type holds the method you are interested in. Maybe you should inspect your own code with a decompiler first, to see how the compiler generated stuff is set up. – thehennyy May 15 '21 at 22:01

1 Answers1

0

This has nothing to do with LINQ. It is purely C# lambda expressions + List.

In this case, Predicate 1is not a *field name*, but instead, a type, more preciselySystem.Predicate`.

It looks to me that you want to analyze method bodies looking for references to this type. Even though this may be possible to achieve through reflection it will be much easier you you can use Mono.Cecil; in that case you can use something like (please, not that this code is not complet in any ways... you'd need to account for errors, static fields, etc):

using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;

class Bar
{
    void Foo(List<int> items)
    {
        // A non sense expression :)
        var r = items.FindAll(i => i % 2 == 0 || i.ToString() == "A");
    }

    static void Main()
    {
        var a = AssemblyDefinition.ReadAssembly(typeof(Bar).Assembly.Location);

        var allMethods = a.MainModule.GetTypes().SelectMany(t => t.Methods);

        // find instructions referencing System.Predicate<T>
        var allMethodsReferencingSystemPredicate = allMethods.Where(m => m.Body.Instructions.Any(i => i.OpCode == OpCodes.Newobj && i.Operand.ToString().Contains("System.Predicate`1")));
        foreach(var m in allMethodsReferencingSystemPredicate)
        {
            System.Console.WriteLine($"Analyzing {m}");
            
            var instReferencingSystemPredicate = m.Body.Instructions.Where(i => i.OpCode == OpCodes.Newobj && i.Operand.ToString().Contains("System.Predicate`1"));            
            foreach(var inst in instReferencingSystemPredicate)
            {
                System.Console.WriteLine($"Checking {inst} / {inst.Previous}");
                if (inst.Previous.OpCode != OpCodes.Ldftn)
                {
                    System.Console.WriteLine($"Something went wrong. Expected LdFnt, got {inst.Previous.OpCode} in instruction {inst.Previous}");
                    continue;
                }
                // get the method being referenced.
                var method = ((MethodReference) inst.Previous.Operand).Resolve();
                
                // Analyze the method body looking for calls to `ToString()` (you will replace this with your own checks...)
                foreach(var methodInst in method.Body.Instructions)
                {
                    //System.Console.WriteLine($"Checking: {methodInst}");
                    if ( (methodInst.OpCode == OpCodes.Callvirt || methodInst.OpCode == OpCodes.Call) && methodInst.Operand.ToString().Contains("ToString()"))
                    {
                        System.Console.BackgroundColor = System.ConsoleColor.DarkCyan;
                        System.Console.WriteLine($"{method} (used in {m}) references ToString(): {methodInst}");
                    } 
                }
            }
        }

    }
}
Vagaus
  • 4,174
  • 20
  • 31
  • Thanks Vagaus for clarifying about lambda expressions. Actually, my lambda expression calls another method - this is the behavior i want to see. I use ` FieldInfo.FieldType.GetMembers() ` to retrieve Methods from this predicate. But even in these methods there are no call instructions that use the name of my member "ArchName" as per the example. – Minho17 May 16 '21 at 16:24
  • Also, I don't want to use Cecil; whereas I'm building my own tool. – Minho17 May 16 '21 at 16:27
  • [Actually, my lambda expression calls another method - this is the behavior i want to see.] That is the reason that in my example my lambda is calling `Int32.ToString()`. [ don't want to use Cecil; whereas I'm building my own tool] Unless your `tool` is a lib to manipulate IL I don't see why you could not use Mono.Cecil :) . You'll either need to use reflection (which I think will require a lot of low level `byte code` -> `il` translation) or some more `high level` lib (like Cecil). You can of course also use "System.Reflection.Metadata" but it will not be easy either. – Vagaus May 16 '21 at 16:38
  • Following up after too long a time. @Vagaus, you were right thoroughly studying CIL was a royal pain. – Minho17 Jan 10 '22 at 03:06
  • Indeed, learning CIL is not an easy task but, at least IMHO, it is interesting :) https://sharplab.io/ is a good resource to see how C# is translated to IL. – Vagaus Jan 10 '22 at 15:08