Этот вопрос потребует небольшого введения.
Я работаю над проектом безопасности, который будет анализировать сборки CIL и отклонять те, которые делают определенные определенные «плохие» вещи, а также позволять хост-приложению предоставлять «ворота» для некоторых методов, чтобы разрешить фильтрацию некоторых вызовов. (Это небольшое подмножество функциональности проекта, но это та часть, о которой я буду здесь расспрашивать.)
Проект сканирует все инструкции в каждом методе сборки и ищет коды операций call, callvirt, ldftn, ldvirtftn и newobj, поскольку это единственные коды операций, которые в конечном итоге могут привести к вызову метода. Коды операций ldftn используются при создании делегатов, например:
ldarg.1
ldftn instance bool string::EndsWith(string)
newobj instance void class [System.Core]System.Func`2<string, bool>::'.ctor'(object, native int)
В конце этой последовательности Func<string, bool>
находится на вершине стека.
Допустим, я хочу перехватывать все звонки на String.EndsWith(String)
. Для call и callvirt я могу просто заменить вызов экземпляра статическим вызовом сигнатуры Boolean(String,String)
-- первым аргументом будет строковый экземпляр, для которого изначально был вызван метод. На уровне CIL поведение будет однозначным и четко определенным, поскольку именно так вызываются статические методы.
А для ldftn? Я попытался просто заменить операнд инструкции ldftn тем же статическим методом, который использовался для замены операнда call/callvirt:
ldarg.1
ldftn bool class Prototype.Program::EndsWithGate(string, string)
newobj instance void class [System.Core]System.Func`2<string, bool>::'.ctor'(object, native int)
Я полностью ожидал, что это не удастся, поскольку делегату предоставляется целевой объект (не нулевой) при передаче указателя статического метода. К моему удивлению, это действительно работает как в среде выполнения Microsoft .NET, так и в Mono. Я понимаю, что параметр target/this является лишь первым параметром метода и скрыт для методов экземпляра. (Проект основан на этих знаниях.) Но тот факт, что делегаты действительно работают в таких условиях, меня немного озадачивает.
Итак, мой вопрос: это определенное и задокументированное поведение? Будут ли делегаты при вызове всегда помещать свою цель в стек, если она не равна нулю? Не лучше ли создать класс замыкания, который будет захватывать цель и «правильно» вызывать статический метод, даже если это будет намного сложнее и раздражает?