Поведение делегата CIL с конфликтующей «статичностью» целевого метода - PullRequest
5 голосов
/ 13 декабря 2010

Этот вопрос потребует небольшого введения.

Я работаю над проектом безопасности, который будет анализировать сборки CIL и отклонять те, которые выполняют определенные определенные "плохие" действия, а также позволяет приложению хостинга предоставлять "gates "для некоторых методов, чтобы разрешить фильтрацию некоторых вызовов.(Это небольшое подмножество функциональных возможностей проекта, но об этой части я буду интересоваться здесь.)

Проект сканирует все инструкции в каждом методе сборки и ищетКоды операций 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)

Я полностью ожидал, что это не удастся, поскольку делегат получает целевой объект (неnull) пока вручается статический указатель метода.К моему удивлению, это на самом деле работает как во время выполнения Microsoft .NET, так и в Mono.Я понимаю, что target / этот параметр является только первым параметром метода и скрыт для экземпляров методов.(Проект основан на этих знаниях.) Но тот факт, что делегаты действительно работают в таких условиях, меня немного озадачивает.

Итак, мой вопрос: это определенное и задокументированное поведение?Будут ли делегаты при вызове всегда помещать свою цель в стек, если она не равна нулю?Было бы лучше создать класс замыкания, который будет захватывать цель и «правильно» вызывать статический метод, даже если это будет намного сложнее и раздражает?

Ответы [ 3 ]

5 голосов
/ 13 декабря 2010

ECMA 335 spec part 2 14.6.2 имеет параграф об этом: Соглашение о вызовах T и D должно точно совпадать, игнорируя различие между статическими и экземплярами методов.(т. е. параметр this, если он есть, специально не обрабатывается).

То, что для меня звучит так, для статических методов будет разрешено в двух вариантах:

  • Без этого вв этом случае NULL должен быть передан
  • С дополнительным первым параметром, предполагая, что тип совпадает с тем, что было передано в вызов newobj.
3 голосов
/ 24 декабря 2010

Стоит отметить, что это не злоупотребление.Это техника, известная как «делегирование карри».Это происходит от более общего метода, называемого «карринг» в функциональных языках программирования, где функция с N + 1 аргументом преобразуется в функцию с N аргументами. Эквивалент C # будет выглядеть примерно так:CLR обеспечивает особую поддержку для случая «карри в первую очередь», главным образом потому, что на уровне машинного кода вызов статического метода карри выглядит почти так же, как вызов метода экземпляра (параметр this передается как неявный первый аргумент).

Это делает реализацию каррирования делегата довольно эффективной.Первоначально он был реализован вместе с DynamicMethod для поддержки Iron Python.Он также используется для других целей, например, позволяет делегатам прозрачно ссылаться на методы расширения.

1 голос
/ 13 декабря 2010

Ну, я не думал, что сам отвечу на свой первый вопрос ...

Коллега из #mono ( Ck ) сообщил мне о соответствующем поведении Delegate.CreateDelegate : (выделено мной)

Если указан аргумент firstArgument, он передается методу при каждом вызове делегата;Считается, что firstArgument привязан к делегату, а делегат закрыт по первому аргументу. Если метод является статическим (Shared в Visual Basic), список аргументов, предоставляемый при вызове делегата, включает все параметры, кроме первого ;если метод является методом экземпляра, то firstArgument передается скрытому параметру экземпляра (представленному этим в C # или Me в Visual Basic).

Мне кажется логичным сделать вывод из этой документациито, что это (ab) использование ldftn во время построения делегата в сочетании с ненулевой целью, на самом деле является четко определенным поведением.

...