Статичекий анализ ипользуемого API
От: SuhanovSergey  
Дата: 11.09.16 17:44
Оценка: 22 (1)
Есть функция-пользователь некоего API. Задача — узнать какие части API эта функция использует. API состоит из графа объектов (интерфейсов). Функция принимает корневой объект. Нужно узнать какие свойства на каких интерфейсах функция вызывает. Анализ статический. Должен быть выполнен до вызова функции. Функций-пользователей много, API один. Функции и API под моим контролем, т.е. к ним можно предъявлять любые требования. Цель данного вопроса — избежать требования к Функциям декларировать, какие части API нужны. Зачем нужно декларировать? Чтобы не инициалировать ненужные части API. Ленивая инициализация чрезвычайно сложна в реализации.
Функция-пользователь может быть сколь угодно сложной, в общем случае она не сводится к Expression. Она может вызывать вспомогательные функции.
Пока что единтственное решение, что я вижу — это декомпиляция и анализ функций-пользователей с помощью Mono.Cecil.
Есть другие варианты?
Re: Статичекий анализ ипользуемого API
От: Sinix  
Дата: 11.09.16 18:09
Оценка:
Здравствуйте, SuhanovSergey, Вы писали:

SS>Есть другие варианты?


Ага. Самый простой — assembly references. Нет ручек — нет конфетки нет ссылки на сборку — нет "лишнего" кода.
Если не получается изолировать зависимости на уровне сборок — делитесь, как можно это сделать уровнем ниже У меня красивых идей нет.

Для vs enterprise (вот не вспомню сходу, есть ли в младших редакциях) есть layer validation с поддержкой кастомной логики


SS>Цель данного вопроса — избежать требования к Функциям декларировать, какие части API нужны. Зачем нужно декларировать? Чтобы не инициалировать ненужные части API. Ленивая инициализация чрезвычайно сложна в реализации.


Ух ты, а вот это интересно. Если не секрет — где такие приседания требуются?


SS>Пока что единтственное решение, что я вижу — это декомпиляция и анализ функций-пользователей с помощью Mono.Cecil.

Вот тут будет в принципе нерешаемая проблема с false negatives,
class A
{
  public event EventHandler SomeEvent;
  
  public void Call() => SomeEvent?.Invoke(this, EventArgs.Empty);
}

Как зависимости определять будем?


UPD, P.S. Если стопроцентных гарантий не требуется, то можно подглядеть код в .net refractor, боль менее то, что вам надо делает. Только учтите, что он кучу ошибок делает, придётся своими тестами проверять.
Отредактировано 11.09.2016 18:17 Sinix . Предыдущая версия .
Re[2]: Статичекий анализ ипользуемого API
От: SuhanovSergey  
Дата: 11.09.16 18:59
Оценка:
Здравствуйте, Sinix, Вы писали:

Спасибо за наводки.

Visualization and Modeling из студии на первый взглад выглядит, как чисто development-time фича. Также это выглядит, как бесполезная тулза, которую выпилят в будущем

SS>>Цель данного вопроса — избежать требования к Функциям декларировать, какие части API нужны. Зачем нужно декларировать? Чтобы не инициалировать ненужные части API. Ленивая инициализация чрезвычайно сложна в реализации.

S>Ух ты, а вот это интересно. Если не секрет — где такие приседания требуются?
Это аналитика по big data. Части API в принципе дешёвы. Но если инстанционировать реализацию API сотню миллионов раз, то выброс каждой ненужной части экономит драгоценные минуты/часы в дата центре.


S>Вот тут будет в принципе нерешаемая проблема с false negatives,

Это да. false negative приведёт к NRE, запрос пофейлится, проблема будет замечена и пофикшена.
Re[2]: Статичекий анализ ипользуемого API
От: SuhanovSergey  
Дата: 11.09.16 19:13
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, SuhanovSergey, Вы писали:


SS>>Есть другие варианты?


S>Ага. Самый простой — assembly references. Нет ручек — нет конфетки нет ссылки на сборку — нет "лишнего" кода.

S>Если не получается изолировать зависимости на уровне сборок — делитесь, как можно это сделать уровнем ниже У меня красивых идей нет.
Имеете в виду прочитать в метаданных, что пользовательская сборка хочет от сборки с API? Да, это может сработать.
Re: Статичекий анализ ипользуемого API
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 11.09.16 22:24
Оценка: 85 (3) +1
Здравствуйте, SuhanovSergey, Вы писали:

SS>Есть другие варианты?

Сразу предупрежу — варианты не продумывал и не проверял даже примерно на реализуемость, поэтому предлагаю чисто на уровне идей.

Общая мысль — декларировать всё же стоит, но делать эту декларацию автоматически в design/compile/deploy time. Короче говоря, когда угодно, но не в runtime.

Теперь где и как делать:
— если вам доступен исходный код, можно собрать все проекты в один Solution и попробовать "натравить" Roslyn.
Т.е. если я правильно понял методику использования API, функции в любом случае должны делать приведение к нужному интерфейсу, прежде чем вызвать нужный им метод. И значит, в той же студии мы могли бы воспользоваться банальным FindAllReferences
Собственно, так и предлагается сделать: взять готовый сервис AbstractFindReferencesService (а точнее его наследники для нужного языка), получить все точки использования нашего интерфейса, а затем попытаться понять, что это за символы, в которых искомый интерфейс используется: методы, классы, ... (опять же сложностей быть не должно — есть готовый функционал определения к какому символу относится конкретная строка кода).

— если исходный код не доступен, то кроме Mono.Cecil можно попробовать воспользоваться библиотекой Common Compiler Infrastructure. Нам пригодится или часть Metadata API, или надстройка над ней Code Model and AST API
Почему именно их? Ну если использовать ту же Code Model (а там можно восстановить по имеющемуся коду его модель) и работать не с IL инструкциями (где вызов метода это сначала несколько инструкций по занесению аргументов в стек, а затем или обычный, или виртуальный call, ...), а с неким псевдокодом, похожим на VB или C#, где будет нормальная конструкция "вызов метода", в которой будет полная спецификация: какой метод какого класса (или интерфейса) вызывается, какие ему передаются аргументы, ...
Причем не надо будет писать свой проходчик по коду, достаточно будет отнаследоваться от стандартного Visitor и перегрузить метод, который срабатывает на нахождение вызова метода (ну и плюс на метод, который срабатывает на анализ самого метода — чтобы знать, где находитесь).
Вроде бы, строить полный граф вызовов методов вам не требуется


Первый, как мне кажется предпочтительнее, ибо Roslyn растет и развивается, документации немало, есть куча форумов где смогут хотябы минимально подсказать, и т.д.
По CCI, к сожалению документации маловато и форум почти загнулся (а ведь на базе неё, как я понимаю, работал и тот же FxCop и вроде даже некая часть Roslyn, связанная с анализом References), но с другой стороны, по той же Mono.Cecil, как выяснилось, документации не сильно больше. Разве что её знатоков всё же попроще найти.

Вот как-то так — если будут вопросы, задавайте, постараюсь ответить (может даже согу накидать простенькие примеры туда и туда, но, боюсь, не в ближайшие дни).
Re: Статичекий анализ ипользуемого API
От: Sinclair Россия https://github.com/evilguest/
Дата: 12.09.16 03:48
Оценка: 6 (1) +1
Здравствуйте, SuhanovSergey, Вы писали:

SS>Есть функция-пользователь некоего API. Задача — узнать какие части API эта функция использует. API состоит из графа объектов (интерфейсов). Функция принимает корневой объект. Нужно узнать какие свойства на каких интерфейсах функция вызывает. Анализ статический. Должен быть выполнен до вызова функции. Функций-пользователей много, API один.

Вообще говоря, если вам не нужны конкретные идентити объектов, то не вижу проблем.
Тупо идём по коду и анализируем использованные metadata tokens.
Ограничения:
1. Нет анализа достижимости. То есть обращения к API из веток if, которые никогда не выполняются, или ещё чего, будут считаться валидными.
2. Нет анализа реальных путей исполнения и identity — то есть вещи вроде такой:
public interface IApiFacade
{
   public interface IDataStorage
   {
     public ICollection People { get;} 
     public ICollection Orders { get;}
   }
   public IDataStorage StorageA {get;}
   public IDataStorage StorageB {get;}
}
...

public void FunctionA(IApiFacade facade)
{
   var p = facade.StorageA.People[0];
   var o = facade.StorageB.Orders[0];
}

Здесь для FunctionA не нужно инициализировать StorageA.Orders и StorageB.People. Однако тупой анализ метаданных покажет только, что функция использует все свойства интерфейсов IApiFacade и IDataStorage.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Статичекий анализ ипользуемого API
От: Pavel Dvorkin Россия  
Дата: 12.09.16 05:14
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Ага. Самый простой — assembly references. Нет ручек — нет конфетки нет ссылки на сборку — нет "лишнего" кода.


А в дотнете динамически эта ссылка не может образоваться в рантайме ?

В WinAPI я бы не стал утверждать, что во время работы не понадобится функция ABC из DLL XYZ, даже если любым статическим анализом доказано, что никаких упоминаний ABC и XYZ в программе нет.
With best regards
Pavel Dvorkin
Отредактировано 12.09.2016 5:14 Pavel Dvorkin . Предыдущая версия .
Re[3]: Статичекий анализ ипользуемого API
От: Sinix  
Дата: 12.09.16 05:17
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>В WinAPI я бы не стал утверждать, что во время работы не понадобится функция ABC из DLL XYZ, даже если любым статическим анализом доказано, что никаких упоминаний ABC и XYZ в программе нет.


Может-может. Но топикстартер написал, что false positives его устраивают.
Re[3]: Статичекий анализ ипользуемого API
От: Sinix  
Дата: 12.09.16 05:40
Оценка:
Здравствуйте, SuhanovSergey, Вы писали:

SS>Visualization and Modeling из студии на первый взглад выглядит, как чисто development-time фича. Также это выглядит, как бесполезная тулза, которую выпилят в будущем

Не, оно ещё и при сборке работает, ссылку я дал. Насчёт бесполезности — местами да, но инструмента получше у MS для нас нет


S>>Ух ты, а вот это интересно. Если не секрет — где такие приседания требуются?

SS>Это аналитика по big data. Части API в принципе дешёвы. Но если инстанционировать реализацию API сотню миллионов раз, то выброс каждой ненужной части экономит драгоценные минуты/часы в дата центре.

Ммда... если API много, то как бы не оказалось дешевле Lazy<T> привернуть. А вообще интересная задача, держите в курсе.


SS>Имеете в виду прочитать в метаданных, что пользовательская сборка хочет от сборки с API? Да, это может сработать.

Ага. Но пока я наводящие вопросы задавал, чтоб фигню не советовать, Михаил Романов уже расписал основные варианты

Если бы зависимости были бы крупными блоками, то всё проще было бы — растащил по сборкам и всё. Если ссылку на сборку с API нет, то можно условно считать, что код из неё не используется.


P.S. Зависимости можно ещё через вот это поискать. По идее это часть .net native toolchain, которая отвечает за убирание неиспользуемого кода при сборке, но я не уверен, что там весь код выложен. В принципе, можно у них же и спросить в issues, отвечают без проблем.
Re[4]: Статичекий анализ ипользуемого API
От: Pavel Dvorkin Россия  
Дата: 12.09.16 05:55
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Может-может. Но топикстартер написал, что false positives его устраивают.


Понятно.

Собственно говоря, если я правильно понял, задача сводится к вариации Depends Walker в применении к дотнету. Там все начинается с секции импорта для EXE, потом по всем DLL из этой секции просмотрт секций импорта их и далее рекурсивно.

А для дотнета есть аналог Depends Walker ? Поиск дал мне рефлектор (но это слишком сложно для такой задачи), и вот это

http://www.ndepend.com/
https://www.netdepends.com/

Кто-то пробовал ?
With best regards
Pavel Dvorkin
Отредактировано 12.09.2016 5:56 Pavel Dvorkin . Предыдущая версия .
Re[5]: Статичекий анализ ипользуемого API
От: Sinix  
Дата: 12.09.16 06:19
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Собственно говоря, если я правильно понял, задача сводится к вариации Depends Walker в применении к дотнету. Там все начинается с секции импорта для EXE, потом по всем DLL из этой секции просмотрт секций импорта их и далее рекурсивно.

Ага. Только топикстартеру надо не на уровне сборок (dll-ек), а на уровне методов. Со всеми приседаниями с замыканиями, await, yield и тыды

PD>А для дотнета есть аналог Depends Walker ? Поиск дал мне рефлектор (но это слишком сложно для такой задачи), и вот это


Да куча их есть, но топикстартеру надо чтоб оно работало само при сборке, а это сильно сужает область поиска

Кстати, ndepend в теории подойдёт, и удобный язык запросов есть, и интеграция с студией, и с билд-серверами дружит. На практике $330 раз в два года на разработчика на не сильно нужную штуку...
Re: Статичекий анализ ипользуемого API
От: SuhanovSergey  
Дата: 12.09.16 14:12
Оценка: 53 (2) +1
Почитал про рекомендованные библиотеки, походил по ним рефлектором. В общем, они могут делать то, что мне нужно, но уж больно они монстроидные.
В результате решил начать с простого прохода по IL, и кажется, оно работает. Причём код довольно простой, если принять ограничения, что весь позьзовательский код должен находится в рамках одного класса.
Демо зависящее только от Mono.Cecil: http://files.rsdn.org/20739/APIUseScanner.zip
Re[2]: Статичекий анализ ипользуемого API
От: Sinix  
Дата: 12.09.16 14:19
Оценка: +1
Здравствуйте, SuhanovSergey, Вы писали:

SS>Демо зависящее только от Mono.Cecil: http://files.rsdn.org/20739/APIUseScanner.zip


Такой совет: напишите сразу тестовые случаи для всего нетривиального — интерфейсы, yield, await, запуск через Task.Run, вызовы через хелперы аля
context.DoInTransaction(()=>
{
  // some code.
});


и тд и тп. А то будет обидно узнать о том, что что-то не ловится уже постфактум
Re: Статичекий анализ ипользуемого API
От: Алексей.  
Дата: 21.10.16 19:32
Оценка:
Здравствуйте, SuhanovSergey,

Задача еще актуальна?
Re[2]: Статичекий анализ ипользуемого API
От: SuhanovSergey  
Дата: 22.10.16 18:16
Оценка:
Здравствуйте, Алексей., Вы писали:

А>Здравствуйте, SuhanovSergey,


А>Задача еще актуальна?


В принцпе, да.

Более менее приемлемое решение найдено, но если есть ещё варианты, буду рад про них услышать.
Re[3]: Статичекий анализ ипользуемого API
От: Алексей.  
Дата: 25.10.16 19:26
Оценка:
Здравствуйте, SuhanovSergey, Вы писали:

SS>Более менее приемлемое решение найдено, но если есть ещё варианты, буду рад про них услышать.


В свободное время я пишу статический анализатор языка C#. Пока реализована (далеко не полностью) генерация CFG и некий выполнитель, который условно бежит по CFG, в т.ч. заходит во вложенные методы.
Поэтому меня интересуеют практические тест-кейсы использования кастомного статического анализатора. Под какие задачи его нужно адаптировать.
Возможно его можно как-то приспособить к вашим задачам.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.