В этой статье я хотел бы рассказать о нескольких проблемах, с которыми пришлось столкнуться разработчикам PVS-Studio при работе над поддержкой новой версии Visual Studio. Кроме того, я постараюсь ответить на вопрос: почему поддержка нашего анализатора C #, основанного на «готовом решении» (в данном случае Roslyn), в некоторых ситуациях обходится дороже, чем наш «самописный» C ++ анализатор.

С выпуском новой Visual Studio - 2017 Microsoft предоставляет большое количество нововведений для своей «флагманской» IDE. К ним относятся:

  • поддержка C # 7.0;
  • поддержка новых версий .NET Core / .NET Standard;
  • поддержка новых возможностей стандартов C ++ 11 и C ++ 14;
  • улучшения в IntelliSense для многих поддерживаемых языков;
  • «Легкая» загрузка проектов и новые методы для мониторинга производительности расширений IDE;
  • новый компонентный установщик и многое другое.

PVS-Studio 6.14, поддерживающий Visual Studio 2017, был выпущен через 10 дней после выпуска IDE. Работа над поддержкой новой поддержки Visual Studio началась намного раньше - в конце прошлого года. Конечно, не все нововведения в Visual Studio связаны с работой PVS-Studio, однако последний релиз этой IDE оказался особенно трудоемким с точки зрения поддержки во всех компонентах нашего продукта. Больше всего пострадали не наш «традиционный» анализатор C ++ (нам удалось довольно быстро поддержать новую версию Visual C ++), а компоненты, отвечающие за взаимодействие с платформой MSBuild и Roslyn (на которой основан наш анализатор C #).

Также новая версия Visual Studio стала первой с того момента, как мы создали анализатор C # в PVS-Studio (который мы выпустили параллельно с первым выпуском Roslyn в Visual Studio 2015), а анализатор C ++ для Windows был более тесно интегрирован с MSBuild. Поэтому из-за трудностей, возникающих при обновлении этих компонентов, поддержка новой VS стала самой трудоемкой в ​​истории нашего продукта.

Решения Microsoft, используемые в PVS-Studio

Скорее всего, вы знаете, что PVS-Studio - это статический анализатор C / C ++ / C #, работающий в Windows и Linux. Что внутри PVS-Studio? В первую очередь это, конечно, кроссплатформенный анализатор C ++, а также (в основном) кроссплатформенные утилиты для его интеграции в различные системы сборки.

Однако большинство наших пользователей на платформе Windows используют стек технологий для разработки программного обеспечения Microsoft, то есть Visual C ++ / C #, Visual Studio, MSBuild и так далее. Для таких пользователей у нас есть средства для работы с анализатором из Visual Studio (плагин IDE) и утилита командной строки для проверки проектов C ++ / C # / MSBuild. Эта же утилита используется нашим плагином VS «под капотом». Эта утилита для анализа структуры проектов напрямую использует MSBuild API. Наш анализатор C # основан на платформе .NET Compiler Platform (Roslyn) и пока доступен только для пользователей Windows.

Итак, мы видим, что на платформе Windows PVS-Studio использует «родные» инструменты Microsoft для интеграции в Visual Studio, анализа системы сборки и кода C #. С выпуском новой версии Visual Studio все эти компоненты также были обновлены.

Изменения в нашем продукте с выпуском Visual Studio 2017

Помимо обновлений MSBuild и Roslyn, Visual Studio получила ряд новых функций, которые сильно повлияли на наш продукт. По совпадению, некоторые из наших компонентов перестали работать; они использовались без каких-либо изменений в нескольких предыдущих выпусках Visual Studio, некоторые из них работают с Visual Studio 2005 (которые мы больше не поддерживаем). Давайте подробнее рассмотрим эти изменения.

Новая процедура установки Visual Studio 2017

Новая компонентная система установки, позволяющая пользователю выбирать только необходимые компоненты, была полностью отвязана от системного реестра Windows. Теоретически это сделало IDE более портативным и позволило установить несколько версий Visual Studio на одну систему.
Однако это коснулось и разработчиков расширений, потому что весь код, позволяющий определять наличие IDE или ее отдельных компонентов, перестал работать.

Теперь разработчикам предлагаются COM-интерфейсы, в частности ISetupConfiguration, которые будут использоваться для получения информации об установленных версиях Visual Studio. Думаю, многие согласятся, что удобство использования COM-интерфейсов не так велико по сравнению с чтением из реестра. И если для кода C # существуют обертки этих интерфейсов, нам придется довольно много поработать над адаптацией нашего установщика на основе InnoSetup. В конце концов, Microsoft заменила одну технологию, специфичную для Windows, другой. На мой взгляд, польза от такого перехода весьма сомнительна, тем более что Visual Studio не смогла полностью отказаться от использования реестра. По крайней мере, в этой версии.

Более существенным следствием такого перехода, помимо довольно субъективного вопроса удобства использования, было то, что он косвенно повлиял на работу библиотек MSBuild 15 и их обратную совместимость с предыдущими версиями MSBuild. Причина в том, что новая версия MSBuild также перестала использовать реестр. Нам пришлось обновить все используемые нами компоненты MSBuild, потому что Roslyn напрямую зависит от них. Подробнее о последствиях этих изменений я расскажу чуть позже.

Изменения в C ++ инфраструктуре MSBuild

Говоря об инфраструктуре MSBuild для C ++, я в первую очередь имею в виду уровень, который отвечает за прямой вызов компилятора при построении проектов Visual C ++. Этот уровень называется PlatformToolset в MSBuild и отвечает за подготовку среды выполнения для компилятора C ++. Система PlatformToolsets также обеспечивает обратную совместимость с предыдущими версиями компиляторов Visual C ++. Он позволяет работать с последней версией MSBuild для создания проектов, в которых используются предыдущие версии визуального компилятора C ++.

Например, вы можете создать проект, использующий компилятор C ++ из Visual Studio 2015 в MSBuild 15 / Visual Studio 2017, если эта версия компилятора установлена ​​в системе. Это может быть весьма полезно, поскольку позволяет сразу использовать новую версию IDE в проекте, без предварительного переноса проекта на новую версию компилятора (что иногда является непростой задачей).

PVS-Studio полностью поддерживает PlatformToolsets и использует «родные» API MSBuild для подготовки среды анализатора C ++, позволяя анализатору проверять исходный код максимально близко к тому, как он скомпилирован.

Такая тесная интеграция с MSBuild позволила нам довольно легко поддерживать новые версии компилятора C ++ от Microsoft. Или, если быть более точным, для поддержки своей среды сборки, поскольку поддержка новых возможностей компилятора (например, нового синтаксиса и файлов заголовков, использующих этот синтаксис) не входит в объем данной статьи. Мы только что добавили новый PlatformToolset в список поддерживаемых.

В новой версии Visual C ++ процедура настройки среды компилятора претерпела существенные изменения, которые снова «сломали» наш код, который ранее работал для всех версий, начиная с Visual Studio 2010. Хотя инструменты платформы из предыдущих версий компилятора являются все еще работая, нам пришлось написать отдельную ветку кода для поддержки нового набора инструментов. По совпадению (а может быть, и нет) разработчики MSBuild также изменили схему именования наборов инструментов C ++: v100, v110, v120, v140 для предыдущих версий и v141 для последней версии (при этом Visual Studio 2017 по-прежнему остается версией 15.0).

В новой версии полностью изменена структура скриптов vcvars, от которых зависит развертывание среды компилятора. Эти сценарии устанавливают переменные среды, необходимые для компилятора, добавляют переменную PATH с путями к двоичным каталогам и системным библиотекам C ++ и так далее. Для анализатора требуется идентичная среда, в частности для предварительной обработки исходных файлов перед началом анализа.

Можно сказать, что эта новая версия сценариев развертывания в некотором роде сделана более «аккуратной» и, скорее всего, ее легче поддерживать и расширять (возможно, обновление этих сценариев было вызвано включением clang в качестве компилятора в новая версия Visual C ++), но с точки зрения разработчиков анализатора C ++ это добавило нам нагрузки.

Анализатор C # PVS-Studio

Вместе с Visual Studio 2017 вышел релиз Roslyn 2.0 и MSBuild 15. Может показаться, что для поддержки этих новых версий в PVS-Studio C # было бы достаточно обновить пакеты Nuget в проектах, которые их используют. После этого нашему анализатору станут доступны все «плюшки» новых версий, такие как поддержка C # 7.0, новых типов проектов .NET Core и так далее.

Действительно, было довольно легко обновить используемые нами пакеты и пересобрать анализатор C #. Однако уже первый запуск новой версии на наших тестах показал, что «сломалось все». Дальнейшие эксперименты показали, что анализатор C # корректно работает только в системе с установленной Visual Studio 2017 / MSBuild 15. Недостаточно наличия в нашем дистрибутиве необходимых версий библиотек Roslyn / MSBuild. Выпуск новой C # версии анализатора «как есть» приведет к ухудшению результатов анализа для пользователей, которые работают с предыдущими версиями компиляторов C #.

Когда мы создавали первую версию анализатора C #, в которой использовался Roslyn 1.0, мы старались сделать наш анализатор «независимым» решением, не требующим установки каких-либо сторонних компонентов. Основное требование к пользовательской системе - это компилируемость анализируемого проекта - если проект может быть построен, он может быть проверен анализатором. Очевидно, что для сборки проектов Visual C # (csproj) в Windows необходимо иметь как минимум MSBuild и компилятор C #.

Мы решили сразу отказаться от идеи обязать наших пользователей устанавливать последние версии MSBuild и Visual C # вместе с анализатором C #. Если проект собирается нормально в Visual Studio 2013 (который, в свою очередь, использует MSBuild 12), требование установки MSBuild 15 будет выглядеть как избыточное. Мы же, наоборот, пытаемся снизить «порог», чтобы начать пользоваться нашим анализатором.

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

Как PVS-Studio взаимодействует с Roslyn

Когда мы только начинали разрабатывать свой анализатор C #, у нас было 2 способа работы с платформой Roslyn.

Первый - использовать Diagnostics API, который был специально разработан для разработки анализаторов .NET. Этот API предоставляет возможность реализовать вашу собственную «диагностику» путем наследования от абстрактного класса DiagnosticAnalyzer. С помощью класса CodeFixProvider пользователи могут реализовать автоматическое исправление таких предупреждений.

Безусловным преимуществом такого подхода является мощность существующей инфраструктуры Roslyn. Правила диагностики становятся доступными в редакторе кода Visual Studio и могут применяться при редактировании кода в среде IDE или при выполнении перестроения проекта. Такой подход не обязывает разработчика анализатора открывать проект и исходные файлы вручную - все будет сделано в рамках «нативного» компилятора, основанного на Roslyn. Если бы мы пошли по этому пути, у нас, вероятно, не было бы проблем с обновлением до нового Roslyn, по крайней мере, в том виде, в каком он есть сейчас.

Второй вариант - реализовать полностью автономный анализатор по аналогии с PVS-Studio C ++. Этот вариант нам показался лучше, так как мы решили сделать инфраструктуру анализатора C # максимально приближенной к существующей C / C ++. Это позволило достаточно быстро адаптировать существующие диагностики C ++ (конечно, не все, но актуальные для C #), а также более продвинутые методы анализа.

Roslyn предоставляет средства, необходимые для такого подхода: мы сами открываем файлы проектов Visual C #, строим синтаксические деревья из исходного кода и реализуем собственный механизм их анализа. Все это можно сделать с помощью API MSBuild и Roslyn. Таким образом, мы получили полный контроль над всеми этапами анализа, независимо от работы компилятора или IDE.

Какой бы заманчивой ни казалась «бесплатная» интеграция с редактором кода Visual Studio, мы предпочли использовать наш собственный интерфейс IDE, поскольку он предоставляет больше возможностей, чем стандартный список ошибок (где будут выдаваться такие предупреждения). Использование API диагностики также ограничило бы нас версиями компилятора, основанными на Roslyn, то есть теми, которые включены в Visual Studio 2015 и 2017, в то время как автономный анализатор позволит нам поддерживать все предыдущие версии.

При создании анализатора C # мы увидели, что Roslyn оказалась очень сильно привязанной к MSBuild. Конечно, я говорю здесь о версии Roslyn для Windows, потому что у нас еще не было возможности поработать с версией для Linux, поэтому я не могу точно сказать, как там все происходит.

Сразу скажу, что API Roslyn для работы с проектами MSBuild остается довольно неполным даже в версии 2.0. При написании анализатора C # нам приходилось часто использовать «клейкую ленту», поскольку Roslyn выполняла несколько вещей неправильно (неправильно означает, что не так, как MSBuild делал бы это при сборке тех же проектов), что, естественно, приводило к ложным срабатываниям и ошибки при анализе исходных файлов.

Именно эти тесные связи Roslyn и MSBuild привели к тем проблемам, с которыми мы столкнулись до обновления Visual Studio 2017.

Roslyn и MSBuild

Для работы анализатора нам необходимо получить от Roslyn две сущности: синтаксическое дерево проверяемого кода и семантическую модель дерева, т.е. семантику синтаксических конструкций, представляющих его узлы - типы полей класса, возвращаемые значения, сигнатуры. методов и так далее. И если для получения синтаксического дерева с Roslyn достаточно иметь только файл исходного кода, то для генерации семантической модели этого файла необходимо скомпилировать проект, который он включает.

Обновление Roslyn до 2.0 привело к появлению ошибок семантической модели на наших тестах (на это указывает сообщение анализатора V051). Такие ошибки обычно проявляются в результате анализа в виде ложноположительных / отрицательных предупреждений, т.е. исчезает часть полезных предупреждений и появляются неправильные предупреждения.

Для получения семантической модели Roslyn предоставляет своим пользователям так называемый Workspace API, который может открывать проекты .NET MSBuild (в нашем случае это csproj и vbproj) и получать «компиляции» таких проектов. В этом контексте мы будем говорить об объекте вспомогательного класса Compilation в Roslyn, который абстрагирует подготовку и вызов компилятора C #. Из такой «компиляции» можно получить семантическую модель. Ошибки компиляции приводят к ошибкам в семантической модели.

Теперь давайте посмотрим, как Roslyn взаимодействует с MSBuild, чтобы получить «компиляцию» проекта. Ниже приведена диаграмма, иллюстрирующая взаимодействие в упрощенном виде:

График разделен на 2 сегмента - PVS-Studio и Build Tools. Сегмент PVS-Studio содержит компоненты, входящие в дистрибутив с нашим анализатором - библиотеки MSBuild и Roslyn, реализующие используемые нами API. Сегмент Build Tools включает в себя инфраструктуру системы сборки, которая должна присутствовать в системе для правильной работы этих API.

После того, как анализатор запросил объект компиляции у Workspace API (для получения семантической модели), Roslyn приступает к сборке проекта или, согласно терминологии MSBuild, выполняет задачу сборки csc. После запуска сборки управление переходит к MSBuild, которая выполняет все подготовительные шаги в соответствии со сценариями сборки.

Следует отметить, что это не «нормальная» сборка (она не приведет к генерации бинарных файлов), а так называемая «конструкция» режима. Конечная цель этого шага для Roslyn - получить всю информацию, которая будет доступна компилятору во время «реальной» сборки. Если сборка привязана к выполнению каких-либо шагов перед сборкой (например, запуск скриптов для автоматического создания некоторых исходных файлов), все такие действия также будут выполняться MSBuild, как если бы это была обычная сборка. .

Получив контроль, MSBuild, а точнее библиотека, входящая в состав PVS-Studio, начнет поиск установленных в системе инструментов сборки. Найдя соответствующий набор инструментов, он попытается создать экземпляры шагов из сценариев сборки. Наборы инструментов соответствуют установленным экземплярам MSBuild, присутствующим в системе. Например, MSBuild 14 (Visual Studio 2015) устанавливает набор инструментов 14.0, MSBuild 12–12.0 и т. Д.

Набор инструментов содержит все стандартные сценарии сборки проектов MSBuild. Файл проекта (например, csproj) обычно содержит только список входных файлов сборки (например, файлы с исходным кодом). Набор инструментов содержит все шаги, которые необходимо выполнить с этими файлами: от компиляции и связывания до публикации результатов сборки. Не будем слишком много говорить о том, как работает MSBuild; просто важно понимать, что одного файла проекта и парсера этого проекта (т.е. той библиотеки MSBuild, входящей в состав PVS-Studio) недостаточно для выполнения полной сборки.

Перейдем к сегменту диаграммы Build Tools. Нас интересует этап сборки csc. MSBuild необходимо будет найти библиотеку, в которой этот шаг будет реализован напрямую, и для этого будет использоваться файл задач из выбранного набора инструментов. Файл задач - это файл xml, содержащий пути к библиотекам, реализующим стандартные задачи сборки. В соответствии с этим файлом будет найдена и загружена соответствующая библиотека, содержащая реализацию задачи csc. Задача csc подготовит все к вызову самого компилятора (обычно это отдельная утилита командной строки csc.exe). Как мы помним, у нас есть «фальшивая» сборка, поэтому, когда все будет готово, вызова компилятора не произойдет. Теперь у Roslyn есть вся необходимая информация для получения семантической модели - все ссылки на другие проекты и библиотеки расширены (поскольку анализируемый исходный код может содержать типы, объявленные в этих зависимостях); все этапы предварительной сборки выполняются, все зависимости восстанавливаются / копируются и так далее.

К счастью, если на одном из этих шагов что-то пошло не так, у Roslyn есть резервный механизм для подготовки семантической модели на основе информации, доступной до начала компиляции, то есть до того момента, когда управление было передано в MSBuild Execution API. Обычно эта информация собирается из оценки файла проекта (которая, по общему признанию, также проводится отдельным API оценки MSBuild). Часто этой информации недостаточно для построения полной семантической модели. Лучшим примером здесь является новый формат проектов .NET Core, в котором сам файл проекта ничего не содержит - даже список исходных файлов, не говоря уже о зависимостях. Но даже в «обычных» файлах .csproj мы видели потерю путей к зависимостям и символам условной компиляции (определениям) после неудачной компиляции, хотя их значения были записаны непосредственно в самом файле проекта.

Что-то пошло не так

Теперь, поскольку, надеюсь, стало немного понятнее, что происходит «внутри» PVS-Studio при проверке проекта C #, давайте посмотрим, что произошло после обновления Roslyn и MSBuild. На графике выше хорошо видно, что часть Build Tools с точки зрения PVS-Studio находится во «внешней среде» и, соответственно, не контролируется анализатором. Как описано ранее, мы отказались от идеи разместить в дистрибутиве весь MSBuild, поэтому нам придется полагаться на то, что будет установлено в системе пользователя. Вариантов может быть много, так как мы поддерживаем работу со всеми версиями Visual C #, начиная с Visual Studio 2010. При этом Roslyn стал основой для компилятора C # начиная с предыдущей версии Visual Studio - 2015.

Рассмотрим ситуацию, когда в системе, на которой запущен анализатор, не установлен MSBuild 15. Анализатор запускается для проверки проекта в среде Visual Studio 2015 (MSBuild 14). И теперь мы видим первый недостаток Roslyn - при открытии проекта MSBuild в нем не указан правильный набор инструментов. Если набор инструментов не указан, MSBuild начинает использовать набор инструментов по умолчанию - в соответствии с используемой версией библиотеки MSBuild. А поскольку Roslyn 2.0 скомпилирован с зависимостью MSBuild 15, библиотека выбирает эту версию набора инструментов.

Из-за того, что этот набор инструментов отсутствует в системе, MSBuild неправильно инстанцирует этот набор инструментов - мы получаем «смесь» несуществующих и неправильных путей, указывающих на набор инструментов версии 4. Почему 4? Потому что этот набор инструментов вместе с 4-й версией MSBuild всегда доступен в системе как часть .NET Framework 4 (в более поздних версиях MSBuild он был отвязан от фреймворка). Результат - выбор неверного файла target, неправильная задача csc и, в конечном итоге, ошибки при составлении семантической модели.

Почему мы не видели такой ошибки на старой версии Roslyn? Во-первых, согласно статистике использования нашего анализатора, у большинства наших пользователей Visual Studio 2015, т.е. правильная (для Roslyn 1.0) версия MSBuild уже установлена.

Во-вторых, новая версия MSBuild, как я упоминал ранее, больше не использует реестр для хранения конфигураций и, в частности, информации об установленном наборе инструментов. И если все предыдущие версии MSBuild сохраняли свои наборы инструментов в реестре, MSBuild 15 теперь хранит их в файле конфигурации рядом с MSBuild.exe. В новом MSBuild изменился «постоянный адрес» - предыдущие версии всегда находились в c: \ Program Files (x 86) \ MSBuild \% VersionNumber%, а новая версия теперь развертывается по умолчанию в каталоге установки Visual Studio (который также изменено по сравнению с предыдущими версиями).

Этот факт иногда «скрывал» неправильно выбранный набор инструментов в предыдущих версиях - семантическая модель сгенерировалась правильно с таким неправильным набором инструментов. Более того, даже если необходимый новый набор инструментов присутствует в системе, используемая нами библиотека может даже не найти - теперь она находится в файле app.config MSBuild.exe, а не в реестре, и библиотека загружается не из процесс MSBuild.exe, но из PVS-Studio_Cmd.exe. В новом MSBuild есть резервный механизм на этот случай. Если в системе установлен COM-сервер, на котором реализована ISetupConfiguration, MSBuild попытается найти набор инструментов в каталоге установки Visual Studio. Однако автономный установщик MSBuild, конечно, не регистрирует этот COM-интерфейс - это делается только установщиком Visual Studio.

И, наконец, третий случай и, наверное, самая важная причина - это, к сожалению, недостаточное тестирование нашего анализатора на различных поддерживаемых конфигурациях, что не позволило нам выявить проблему ранее. Оказалось, что на всех машинах для повседневного тестирования у нас установлена ​​Visual Studio 2015 \ MSBuild 14. К счастью, мы смогли выявить и устранить проблему до того, как наши клиенты сообщили нам о ней.

Как только мы поняли, почему Roslyn не работает, мы решили попробовать указать правильный набор инструментов при открытии проекта. Какой набор инструментов считать «правильным» - это отдельный вопрос? Мы думали об этом, когда начали использовать те же API MSBuild для открытия проектов C ++ для нашего анализатора C ++. Поскольку мы можем посвятить этому вопросу целую статью, мы не будем сейчас на ней останавливаться. К сожалению, Roslyn не позволяет указать, какой набор инструментов будет использоваться, поэтому нам пришлось изменить его собственный код (дополнительные неудобства для нас, потому что невозможно просто взять готовые к использованию пакеты Nuget). После этого в нескольких проектах из нашей тестовой базы исчезли проблемы. Однако проблемы возникали в большем количестве проектов. Что сейчас пошло не так?

Здесь следует отметить, что все процессы, описанные на схеме выше, происходят в рамках единого процесса операционной системы - PVS-Studio_Cmd.exe. Выяснилось, что при выборе правильного набора инструментов возник конфликт при загрузке модулей dll. В нашей тестовой версии используется Roslyn 2.0, который включает библиотеку Microsoft.CodeAnalysis.dll, которая также имеет версию 2.0. На момент начала анализа проекта библиотека уже загружена в память процесса PVS-Studio_Cmd.exe (наш анализатор C #). Проверяя Visual Studio 2015, мы указываем набор инструментов 14.0 при открытии проекта. Далее MSBuild находит нужный файл задач и запускает компиляцию. Поскольку компилятор C # в этом наборе инструментов (напомним, что мы используем Visual Studio 2015) использует Roslyn 1.3, MSBuild пытается загрузить Microsoft.CodeAnalysis.dll версии 1.3 в память процесса. Этого не происходит, так как модуль более поздней версии уже загружен.

Что мы можем сделать в этой ситуации? Стоит ли пытаться получить семантическую модель в отдельном процессе или в домене приложения? Но для получения модели нам понадобится Roslyn (т.е. все те библиотеки, которые вызывают конфликт), но перенос модели из одного процесса / домена в другой может быть нетривиальной задачей, так как этот объект содержит ссылки на компиляции и рабочие области. , из которого он был получен.

Лучшим вариантом было бы переместить анализатор C # в отдельный серверный процесс из нашего синтаксического анализатора решений, который является общим для анализаторов C ++ и C #, и создать две версии таких серверных программ с использованием Roslyn 1.0 и 2.0 соответственно. Но у этого решения есть и существенные недостатки:

  • необходимость его реализации в коде (что означает дополнительные затраты на разработку и задержку выхода новой версии анализатора);
  • более сложная разработка кода диагностических правил (нам придется использовать в коде idefs для поддержки нового синтаксиса из C # 7.0);
  • и, наверное, самое главное - такой подход не защищает нас от выпуска новых промежуточных версий Roslyn.

Разрешите пояснить последний пункт более подробно. За время существования Visual Studio 2015 было 3 обновления, в каждом из которых тоже обновлялся компилятор Roslyn - с 1.0 до 1.3. В случае обновления, например, до версии 2.1, нам придется создавать отдельные версии бэкэнда анализатора на случай каждого минорного обновления студии, иначе возможность повторения ошибки конфликта версий останется для пользователи, у которых установлена ​​не последняя версия Visual Studio.

Следует отметить, что компиляция не удалась и в тех случаях, когда мы пытались работать с инструментами, не использующими Roslyn, например, версия 12.0 (Visual Studio 2013). Была другая причина, но мы не стали копать глубже, потому что проблем, которые мы уже видели, было достаточно, чтобы отказаться от этого решения.

Как мы решили проблему обратной совместимости анализатора со старыми проектами на C #

Покопавшись в причинах этих ошибок, мы увидели необходимость «снабдить» набор инструментов версии 15.0 анализатором. Это избавляет нас от проблем с конфликтом версий между компонентами Roslyn и позволяет проверять проекты для всех предыдущих версий Visual Studio (последняя версия компилятора обратно совместима со всеми предыдущими версиями языка C #). Чуть ранее я уже описывал, почему мы решили не тянуть наш установщик «полноценный» MSBuild 15:

  • большой размер загрузки в веб-установщике MSBuild;
  • потенциальные конфликты версий после обновлений в Visual Studio 2017;
  • неспособность библиотек MSBuild найти собственный установочный каталог (с набором инструментов), если нет Visual Studio 2017.

Однако, исследуя проблемы, возникшие в Roslyn при компиляции проектов, мы поняли, что в нашем дистрибутиве уже есть все необходимые библиотеки Roslyn и MSBuild (напомню, что Roslyn выполняет «фальшивую» компиляцию, поэтому компилятор csc .exe не было необходимости). Фактически, для полноценного набора инструментов нам не хватало нескольких файлов свойств и целей, в которых описывается этот набор инструментов. А это простые xml-файлы в форматах проектов MSBuild, которые в целом занимают всего несколько мегабайт - у нас нет проблем с включением этих файлов в дистрибутив.

Фактически основной проблемой была необходимость «обмануть» библиотеки MSBuild и заставить их принимать «наш» набор инструментов как нативный. Это комментарий в коде MSBuild: Работа без определенных наборов инструментов. Большая часть функциональности ограничена. Скорее всего не сможет построить или оценить проект. (например, ссылка на Microsoft. *. dll без определения набора инструментов или установленного экземпляра Visual Studio). Этот комментарий описывает режим, в котором работает библиотека MSBuild, когда она добавляется в проект в качестве ссылки и не используется из MSBuild.exe. И этот комментарий звучит не слишком обнадеживающе, особенно в части «Скорее всего, не удастся создать или оценить проект».

Итак, как мы можем заставить библиотеки MSBuild 15 использовать сторонний набор инструментов? Напомню, что этот набор инструментов объявлен в app.config файла MSBuild.exe. Оказалось, что вы можете добавить содержимое конфига в конфиг нашего приложения (PVS-Studio_Cmd.exe) и задать для нашего процесса переменную окружения MSBUILD_EXE_PATH с путем к нашему исполняемому файлу. И этот способ сработал! На тот момент последняя версия MSBuild находилась в Release Candidate 4. На всякий случай мы решили посмотреть, как идут дела в основной ветке MSBuild на Github. И как будто по закону Мерфи, в ветке master в коде выбора набора инструментов добавлена ​​проверка - брать набор инструментов из appconfig только в том случае, если имя исполняемого файла - MSBuild.exe. Таким образом, в нашем дистрибутиве появился файл размером 0 байт с именем MSBuild.exe, на который указывает переменная окружения MSBUILD_EXE_PATH процесса PVS-Studio_Cmd.exe.

На этом наши проблемы с MSBuild не закончились. Оказалось, что самого набора инструментов недостаточно для проектов, использующих расширения MSBuild - это дополнительные шаги сборки. Например, к этим типам проектов относятся проекты WebApplication, Portable .NET Core. При установке соответствующего компонента в Visual Studio эти расширения определяются в отдельном каталоге рядом с MSBuild. В нашей «установке» MSBuild его нет. Мы нашли решение благодаря возможности легко изменять «свой» набор инструментов. Для этого мы привязали пути поиска (свойство MSBuildExtensionsPath) нашего набора инструментов к специальной переменной среды, которую процесс PVS-Studio_Cmd.exe определяет в соответствии с типом проверяемого проекта. Например, если у нас есть проект WebApplication для Visual Studio 2015, мы (предполагая, что проект пользователя компилируем) ищем расширения для набора инструментов версии 14.0 и указываем путь к ним в нашей специальной переменной среды. Эти пути нужны MSBuild только для включения дополнительных props \ target в сценарий сборки, поэтому проблем с конфликтами версий не возникло.

В результате анализатор C # может работать в системе с любой из поддерживаемых версий Visual Studio, независимо от версии MSBuild. Потенциальными проблемами может быть наличие пользовательских модификаций сценариев сборки MSBuild, но, благодаря независимости нашего набора инструментов, при необходимости эти модификации могут быть внесены в набор инструментов PVS-Studio.

Плагин PVS-Studio, Легкая загрузка решения

Одной из новых возможностей Visual Studio 2017, позволяющих оптимизировать работу с решениями, содержащими большое количество проектов, стал режим отложенной загрузки - «облегченная загрузка решения».

Этот режим можно включить в IDE как для отдельного решения, так и для всех открытых решений. Особенностью использования режима загрузка облегченного решения является отображение только дерева проектов (без загрузки проектов) в проводнике Visual Studio. Загрузка выбранного проекта (расширение его внутренней структуры и загрузка файлов в проекте) осуществляется только по запросу: после соответствующего действия пользователя (раскрытие узла проекта в дереве) или программно. Подробное описание загрузки легкого раствора приведено в документации.

Однако при создании поддержки этого режима мы столкнулись с рядом проблем:

  • очевидные вопросы, связанные с отсутствием информации о файлах, содержащихся в проекте, до момента загрузки проекта в IDE;
  • необходимость использования новых методов для получения указанной информации;
  • ряд «подводных камней» связан с тем, что мы начали работу по поддержке плагина PVS-Studio в Visual Studio 2017 задолго до его выпуска.

В этой связи хотелось бы отметить недостаточное количество технической документации по использованию облегченного решения load. Фактически вся документация, показывающая особенности внутренних механизмов работы с новыми возможностями VIsual Studio 2017, связанными с загрузкой облегченного решения, ограничена одной статьей.

Говоря об этих «ловушках», пока RC Visual Studio дорабатывалась, Microsoft не только устранила дефекты, но и переименовала некоторые методы в недавно добавленных интерфейсах, которые мы также использовали. В результате нам потребовалось скорректировать рабочий механизм поддержки загрузки облегченного решения после выхода PVS-Studio.

Почему в релизной версии? Дело в том, что один из используемых нами интерфейсов оказался объявлен в библиотеке, входящей в Visual Studio два раза - один в основной установке Visual Studio, а второй - в составе пакета Visual Studio SDK (пакет для разработки расширений Visual Studio). По какой-то причине разработчики Visual Studio SDK не обновили RC-версию этой библиотеки в выпуске Visual Studio 2017. Поскольку SDK у нас был установлен практически на всех машинах (также на машине, на которой выполняются ночные сборки - он также используется в качестве билд-сервера) у нас не возникло никаких проблем при компиляции и работе. К сожалению, эта ошибка была исправлена ​​после выхода PVS-Studio, когда мы получили отчет об ошибке от пользователя. Что касается статьи, о которой я писал ранее, упоминания об этом интерфейсе на момент публикации этого текста все еще имеют старое название.

Вывод

Релиз Visual Studio 2017 стал самым «дорогим» для PVS-Studio с момента его создания. Это было вызвано несколькими факторами - существенными изменениями в работе MSBuild \ Visual Studio, включением анализатора C # в состав PVS-Studio (который теперь тоже нуждается в поддержке).

Когда мы начали работу над статическим анализатором для C #, мы ожидали, что Roslyn позволит сделать это очень быстро. Эти ожидания по большей части оправдались - первая версия анализатора была выпущена всего через 4 месяца. Мы также подумали, что, по сравнению с нашим анализатором C ++, использование стороннего решения позволит нам сэкономить на поддержке новых возможностей языка C #, появляющихся в процессе его дальнейшей эволюции. Это ожидание также подтвердилось. Несмотря на все это, использование готовой платформы для статического анализа оказалось не таким «безболезненным», как мы сейчас видим из нашего опыта поддержки новых версий Roslyn / Visual Studio. Решая вопросы о совместимости с новыми возможностями C #, Roslyn создает трудности в совершенно других сферах из-за того, что он привязан к сторонним компонентам (точнее, MSBuild и Visual Studio). Связь между Roslyn и MSBuild существенно затрудняет его использование в автономном статическом анализаторе кода.

Нас часто спрашивают, почему бы нам не «переписать» наш анализатор C ++ на основе какого-нибудь готового решения, например Clang. В самом деле, это позволило бы нам устранить ряд сегодняшних проблем в нашем ядре C ++. Однако, помимо необходимости переписать существующие механизмы и диагностику, не стоит забывать и о том, что при использовании стороннего решения всегда будут «подводные камни», которые невозможно будет предвидеть.