Этот вопрос потребует небольшого введения.
Я работаю над проект безопасности, который будет анализировать сборки CIL и отклонять те, которые делают определенные «плохие» вещи, а также позволяет хост-приложению предоставлять «ворота» для некоторых методов, чтобы разрешить фильтрацию некоторых вызовов. (Это небольшая часть функциональности проекта, но это та часть, о которой я буду спрашивать здесь.)
Проект сканирует все инструкции в каждом методе сборки и ищет вызов 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.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)
Я полностью ожидал, что это не удастся, так как делегату предоставляется целевой объект (не null) при передаче статического указатель метода. К моему удивлению, это действительно работает как в среде выполнения Microsoft .NET, так и в Mono. Я понимаю, что параметр target / this - всего лишь первый параметр метода и скрыт, например, для методов. (Проект основан на этом знании.) Но тот факт, что делегаты действительно работают в этих обстоятельствах, немного озадачивает меня.
Итак, мой вопрос: это определенное и задокументированное поведение? Будет ли делегат при вызове, всегда помещать свою цель в стек, если она не равна нулю? Было бы лучше создать закрывающий класс, который будет захватывать цель и «должным образом» вызывать статический метод, даже если это будет намного сложнее и раздражает?