﻿# PVS\-Studio теперь в Chocolatey: проверка Chocolatey из\-под Azure DevOps

Мы продолжаем делать использование PVS\-Studio удобнее\. Теперь наш анализатор доступен в Chocolatey, пакетном менеджере для Windows\. Мы полагаем, что это облегчит развёртывание PVS\-Studio, в частности, в облачных сервисах\. Чтобы не идти далеко, проверим исходный код всё того же Chocolatey\. В качестве CI системы выступит Azure DevOps\.

![0712_Chocolatey_ru/image1.png](https://import.viva64.com/docx/blog/0712_Chocolatey_ru/image1.png)

Вот список других наших статей на тему интеграции с облачными системами:

* [PVS\-Studio идёт в облака: Azure DevOps](https://pvs-studio.ru/ru/blog/posts/csharp/0670/)
* [PVS\-Studio идёт в облака: Travis CI](https://pvs-studio.ru/ru/blog/posts/cpp/0636/)
* [PVS\-Studio идёт в облака: CircleCI](https://pvs-studio.ru/ru/blog/posts/cpp/0676/)
* [PVS\-Studio идёт в облака: GitLab CI/CD](https://pvs-studio.ru/ru/blog/posts/cpp/0686/)

Советую обратить внимание на первую статью про интеграцию с Azure DevOps, так как в данном случае некоторые моменты опущены, чтобы не дублироваться\.

Итак, герои данной статьи:

[PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) — инструмент статического анализа кода, предназначенный для выявления ошибок и потенциальных уязвимостей в программах, написанных на языках С, C\+\+, C\# и Java\. Работает в 64\-битных системах на Windows, Linux и macOS, и может анализировать код, предназначенный для 32\-битных, 64\-битных и встраиваемых ARM платформ\. Если вы впервые будете пробовать статический анализ кода для проверки своих проектов, то рекомендуем ознакомиться со [статьёй](https://pvs-studio.ru/ru/blog/posts/cpp/0633/) о том, как быстро посмотреть самые интересные предупреждения PVS\-Studio и оценить возможности этого инструмента\.

[Azure DevOps](https://azure.microsoft.com/en-in/services/devops/) \- набор облачных сервисов, совместно охватывающих весь процесс разработки\. В состав данной платформы входят такие инструменты, как Azure Pipelines, Azure Boards, Azure Artifacts, Azure Repos, Azure Test Plans, позволяющие ускорить процесс создания программного обеспечения и повысить его качество\.

[Chocolatey](https://chocolatey.org/) – пакетный менеджер для Windows с открытым исходным кодом\. Цель проекта \- автоматизировать весь жизненный цикл программного обеспечения от установки до обновления и удаления в операционных системах Windows\.

## Об использовании Chocolatey

Посмотреть, как установить сам пакетный менеджер, вы можете по этой [ссылке](https://chocolatey.org/docs/installation)\. Полная документация по установке анализатора доступна по [ссылке](https://pvs-studio.ru/ru/docs/manual/0025/) в разделе "Установка с использованием пакетного менеджера Chocolatey"\. Вкратце повторю некоторые моменты оттуда\.

Команда для установки последней версии анализатора:

```cpp
choco install pvs-studio
```

Команда установки конкретной версии пакета PVS\-Studio:

```cpp
choco install pvs-studio --version=7.05.35617.2075
```

По умолчанию устанавливается только ядро анализатора \- компонент Core\. Все остальные флаги \(Standalone, JavaCore, IDEA, MSVS2010, MSVS2012, MSVS2013, MSVS2015, MSVS2017, MSVS2019\) можно передать при помощи \-\-package\-parameters\.

Пример команды, которая установит анализатор с плагином для Visual Studio 2019:

```cpp
choco install pvs-studio --package-parameters="'/MSVS2019'"
```

Теперь посмотрим на пример удобного использования анализатора под Azure DevOps\.

## Настройка

Напоминаю, что про такие моменты, как регистрация учётной записи, создание Build Pipeline и синхронизация учётной записи с проектом, лежащим в репозитории на GitHub, есть отдельная [статья](https://pvs-studio.ru/ru/blog/posts/csharp/0670/)\. Наша же настройка сразу начнётся с написания конфигурационного файла\.

Для начала настроим триггер запуска, указав, что производим запуск только для изменений в _master_ ветке:

```cpp
trigger:
- master
```

Далее нам нужно выбрать виртуальную машину\. На данный момент это будет Microsoft\-hosted агент с Windows Server 2019 и Visual Studio 2019:

```cpp
pool:
  vmImage: 'windows-latest'
```

Перейдём к телу конфигурационного файла \(блок _steps_\)\. Несмотря на то, что в виртуальную машину нельзя установить произвольное ПО, я не стал добавлять Docker контейнер\. Мы можем добавить Chocolatey как расширение для Azure DevOps\. Для этого перейдём по [ссылке](https://marketplace.visualstudio.com/items?itemName=gep13.chocolatey-azuredevops)\. Жмём _Get it free_\. Далее, если вы уже авторизированы, просто выбираем свою учётную запись, а если нет, то проделываем всё то же самое после авторизации\.

![0712_Chocolatey_ru/image2.png](https://import.viva64.com/docx/blog/0712_Chocolatey_ru/image2.png)

Тут нужно выбрать, куда мы добавим расширение, и нажать кнопку _Install_\.

![0712_Chocolatey_ru/image3.png](https://import.viva64.com/docx/blog/0712_Chocolatey_ru/image3.png)

После благополучной установки нажмём _Proceed to organization_:

![0712_Chocolatey_ru/image4.png](https://import.viva64.com/docx/blog/0712_Chocolatey_ru/image4.png)

Теперь можно увидеть шаблон для задачи Chocolatey в окне _tasks_ при редактировании конфигурационного файла _azure\-pipelines\.yml:_

![0712_Chocolatey_ru/image5.png](https://import.viva64.com/docx/blog/0712_Chocolatey_ru/image5.png)

Нажмём на Chocolatey и увидим список полей:

![0712_Chocolatey_ru/image6.png](https://import.viva64.com/docx/blog/0712_Chocolatey_ru/image6.png)

Здесь нам нужно выбрать _install_ в поле с командами\. В _Nuspec File Name_ укажем название нужного пакета – pvs\-studio\. Если не указать версию, установится последняя, что нас полностью устраивает\. Нажмём на кнопку _add_ и увидим сформировавшуюся задачу в файле конфигурации\.

```cpp
steps:
- task: ChocolateyCommand@0
  inputs:
    command: 'install'
    installPackageId: 'pvs-studio'
```

Далее перейдём к основной части нашего файла:

```cpp
- task: CmdLine@2
  inputs:
    script:
```

Теперь нам нужно создать файл с лицензией анализатора\. Здесь _PVSNAME_ и _PVSKEY_ – названия переменных, значения которых мы указываем в настройках\. Они будут хранить логин и лицензионный ключ PVS\-Studio\. Чтобы установить их значения, откроем меню _Variables\-\>New variable_\. Создадим переменные _PVSNAME_ для логина и _PVSKEY_ для ключа анализатора\. Не забудьте поставить галочку _Keep this value secret _для _PVSKEY_\. Код команды:

```cpp
сall "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" credentials 
–u $(PVSNAME) –n $(PVSKEY)
```

Соберём проект при помощи bat\-файла, лежащего в репозитории:

```cpp
сall build.bat
```

Создадим папку, где будут лежать файлы с результатами работы анализатора:

```cpp
сall mkdir PVSTestResults
```

Запустим анализ проекта:

```cpp
сall "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" 
–t .\src\chocolatey.sln –o .\PVSTestResults\Choco.plog
```

Конвертируем наш отчёт в формат html при помощи утилиты PlogСonverter:

```cpp
сall "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe" 
–t html –o \PVSTestResults\ .\PVSTestResults\Choco.plog
```

Теперь нужно создать задание для того, чтобы можно было выгрузить отчёт\.

```cpp
- task: PublishBuildArtifacts@1
  inputs:
    pathToPublish: PVSTestResults
    artifactName: PVSTestResults
    condition: always()
```

Полный файл конфигурации выглядит так:

```cpp
trigger:
- master

pool:
  vmImage: 'windows-latest'

steps:
- task: ChocolateyCommand@0
  inputs:
    command: 'install'
    installPackageId: 'pvs-studio'

- task: CmdLine@2
  inputs:
    script: |
      call "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" 
      credentials –u $(PVSNAME) –n $(PVSKEY)
      call build.bat
      call mkdir PVSTestResults
      call "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" 
      –t .\src\chocolatey.sln –o .\PVSTestResults\Choco.plog
      call "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe" 
      –t html –o .\PVSTestResults\ .\PVSTestResults\Choco.plog

- task: PublishBuildArtifacts@1
  inputs:
    pathToPublish: PVSTestResults
    artifactName: PVSTestResults
    condition: always()
```

Нажмём _Save\-\>Save\-\>Run_ для запуска задачи\. Выгрузим отчёт, зайдя во вкладку задачи\.

![0712_Chocolatey_ru/image8.png](https://import.viva64.com/docx/blog/0712_Chocolatey_ru/image8.png)

Проект Chocolatey содержит всего 37615 строк C\# кода\. Рассмотрим некоторые из найденных ошибок\.

## Результаты проверки

**Предупреждение N1**

Предупреждение анализатора: [V3005](https://pvs-studio.ru/ru/docs/warnings/v3005/) The 'Provider' variable is assigned to itself\. CrytpoHashProviderSpecs\.cs 38

```cpp
public abstract class CrytpoHashProviderSpecsBase : TinySpec
{
  ....
  protected CryptoHashProvider Provider;
  ....
  public override void Context()
  {
    Provider = Provider = new CryptoHashProvider(FileSystem.Object);
  }
}
```

Анализатор обнаружил присваивание переменной самой себе, что не имеет смысла\. Скорее всего, на месте одной из этих переменных должна быть какая\-то другая\. Ну или это опечатка, и лишнее присваивание можно просто удалить\.

**Предупреждение N2**

Предупреждение анализатора: [V3093](https://pvs-studio.ru/ru/docs/warnings/v3093/) \[CWE\-480\] The '&' operator evaluates both operands\. Perhaps a short\-circuit '&&' operator should be used instead\. Platform\.cs 64

```cpp
public static PlatformType get_platform()
{
  switch (Environment.OSVersion.Platform)
  {
    case PlatformID.MacOSX:
    {
      ....
    }
    case PlatformID.Unix:
    if(file_system.directory_exists("/Applications")
      & file_system.directory_exists("/System")
      & file_system.directory_exists("/Users")
      & file_system.directory_exists("/Volumes"))
      {
        return PlatformType.Mac;
      }
        else
          return PlatformType.Linux;
    default:
      return PlatformType.Windows;
  }
}
```

Отличие оператора _&_ от оператора _&&_ заключается в том, что если левая часть выражения \- _false_, то всё равно будет вычислена правая часть, что в данном случае подразумевает лишние вызовы метода _system\.directory\_exists_\.

В рассмотренном фрагменте это мелкий недочёт\. Да, данное условие можно оптимизировать, заменив оператор & на оператор &&, но, с практической точки зрения, это ни на что не влияет\. Однако, в других случаях путаница между & и && может вызывать серьезные проблемы, когда правая часть выражения будет работать с некорректными/недопустимыми значения\. Например, в нашей коллекции ошибок, [выявленных с помощью диагностики V3093](https://pvs-studio.ru/ru/blog/examples/v3093/), есть вот такой случай:

```cpp
if ((k < nct) & (s[k] != 0.0))
```

Даже если индекс _k_ некорректен, он будет использоваться для доступа к элементу массива\. В результате будет сгенерировано исключение _IndexOutOfRangeException_\.

**Предупреждения N3, N4**

Предупреждение анализатора: [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) \[CWE\-571\] Expression 'shortPrompt' is always true\. InteractivePrompt\.cs 101

Предупреждение анализатора: [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) \[CWE\-571\] Expression 'shortPrompt' is always true\. InteractivePrompt\.cs 105

```cpp
public static string 
prompt_for_confirmation(.... bool shortPrompt = false, ....)
{
  ....
  if (shortPrompt)
  {
    var choicePrompt = choice.is_equal_to(defaultChoice) //1
    ?
    shortPrompt //2
    ?
    "[[{0}]{1}]".format_with(choice.Substring(0, 1).ToUpperInvariant(), //3
    choice.Substring(1,choice.Length - 1))
    :
    "[{0}]".format_with(choice.ToUpperInvariant()) //0
    : 
    shortPrompt //4
    ? 
    "[{0}]{1}".format_with(choice.Substring(0,1).ToUpperInvariant(), //5
    choice.Substring(1,choice.Length - 1)) 
    :
    choice; //0
    ....
  }
  ....
}
```

В данном случае имеет место быть странная логика работы тернарного оператора\. Рассмотрим подробнее: если выполнится условие, помеченное мной цифрой 1, то мы перейдём к условию 2, которое всегда _true_, а значит выполнится строчка 3\. Если же условие 1 окажется ложным, то мы перейдём на строчку, помеченную цифрой 4, условие в которой тоже всегда _true_, а значит, выполнится строчка 5\. Таким образом, условия, помеченные комментарием 0, никогда не будут выполнены, что может являться не совсем той логикой работы, на которую рассчитывал программист\.

**Предупреждение N5**

Предупреждение анализатора: [V3123](https://pvs-studio.ru/ru/docs/warnings/v3123/) \[CWE\-783\] Perhaps the '?:' operator works in a different way than it was expected\. Its priority is lower than priority of other operators in its condition\. Options\.cs 1019

```cpp
private static string GetArgumentName (...., string description)
{
  string[] nameStart;
  if (maxIndex == 1)
  {
    nameStart = new string[]{"{0:", "{"};
  }
  else
  {
    nameStart = new string[]{"{" + index + ":"};
  }
  for (int i = 0; i < nameStart.Length; ++i) 
  {
    int start, j = 0;
    do 
    {
      start = description.IndexOf (nameStart [i], j);
    } 
    while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false);
    ....
    return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
  }
}
```

Диагностика сработала на строку:

```cpp
while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false)
```

Так как переменная _j_ на несколько строк выше инициализируется нулём, тернарный оператор вернёт значение _false_\. Из\-за данного условия, тело цикла выполнится только один раз\. Мне кажется, что данный фрагмент кода работает совсем не так, как задумывал программист\.

**Предупреждение N6**

Предупреждение анализатора: [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) \[CWE\-571\] Expression 'installedPackageVersions\.Count \!\= 1' is always true\. NuGetService\.cs 1405

```cpp
private void remove_nuget_cache_for_package(....)
{
  if (!config.AllVersions && installedPackageVersions.Count > 1)
  {
    const string allVersionsChoice = "All versions";
    if (installedPackageVersions.Count != 1)
    {
      choices.Add(allVersionsChoice);
    }
    ....
  }
  ....
}
```

Здесь странное вложенное условие: _installedPackageVersions\.Count \!\= 1_, которое всегда будет _true_\. Часто такое предупреждение указывает на логическую ошибку в коде, а в остальных случаях просто на избыточную проверку\.

**Предупреждение N7**

Предупреждение анализатора: [V3001](https://pvs-studio.ru/ru/docs/warnings/v3001/) There are identical sub\-expressions 'commandArguments\.contains\("\-apikey"\)' to the left and to the right of the '\|\|' operator\. ArgumentsUtility\.cs 42

```cpp
public static bool arguments_contain_sensitive_information(string
 commandArguments)
{
  return commandArguments.contains("-install-arguments-sensitive")
  || commandArguments.contains("-package-parameters-sensitive")
  || commandArguments.contains("apikey ")
  || commandArguments.contains("config ")
  || commandArguments.contains("push ")
  || commandArguments.contains("-p ")
  || commandArguments.contains("-p=")
  || commandArguments.contains("-password")
  || commandArguments.contains("-cp ")
  || commandArguments.contains("-cp=")
  || commandArguments.contains("-certpassword")
  || commandArguments.contains("-k ")
  || commandArguments.contains("-k=")
  || commandArguments.contains("-key ")
  || commandArguments.contains("-key=")
  || commandArguments.contains("-apikey")
  || commandArguments.contains("-api-key")
  || commandArguments.contains("-apikey")
  || commandArguments.contains("-api-key");
}
```

Программист, который написал данный участок кода, скопипастил две последние строчки и забыл их отредактировать\. Из\-за этого пользователи Chocolatey лишились возможности применить параметр _apikey_ ещё парой способов\. Аналогично параметрам выше, могу предложить такие варианты:

```cpp
commandArguments.contains("-apikey=");
commandArguments.contains("-api-key=");
```

Copy\-paste ошибки имеют большой шанс рано или поздно появиться в любом проекте с большим количеством исходного кода, и одно из лучших средств борьбы с ними – статический анализ\.

P\.S\. И как всегда, эта ошибка тяготеет появиться в конце многострочного условия :\)\. См\. публикацию "[Эффект последней строки](https://pvs-studio.ru/ru/blog/posts/cpp/0260/)"\.

**Предупреждение N8**

Предупреждение анализатора: [V3095](https://pvs-studio.ru/ru/docs/warnings/v3095/) \[CWE\-476\] The 'installedPackage' object was used before it was verified against null\. Check lines: 910, 917\. NuGetService\.cs 910

```cpp
public virtual ConcurrentDictionary<string, PackageResult> get_outdated(....)
{
  ....
  var pinnedPackageResult = outdatedPackages.GetOrAdd(
    packageName, 
    new PackageResult(installedPackage, 
                      _fileSystem.combine_paths(
                        ApplicationParameters.PackagesLocation, 
                        installedPackage.Id)));
  ....
  if (   installedPackage != null
      && !string.IsNullOrWhiteSpace(installedPackage.Version.SpecialVersion) 
      && !config.UpgradeCommand.ExcludePrerelease)
  {
    ....
  }
  ....
}
```

Классическая ошибка: вначале объект _installedPackage_ используется, а потом проверяется на _null_\. Данная диагностика говорит нам об одной из двух проблем в программе: либо _installedPackage_ никогда не равен _null_, что сомнительно, и тогда проверка избыточна, либо мы потенциально можем получить серьёзную ошибку в коде – попытку доступа по нулевой ссылке\.

## Заключение

Вот мы и проделали ещё один маленький шаг – теперь пользоваться PVS\-Studio стало ещё проще и удобнее\. Также хочу сказать, что Chocolatey – хороший пакетный менеджер с небольшим количеством ошибок в коде, которых могло бы стать ещё меньше при использовании PVS\-Studio\. 

Приглашаем [скачать](https://pvs-studio.ru/ru/pvs-studio/download/) и попробовать PVS\-Studio\. Регулярное использование статического анализатора повысит качество и надёжность разрабатываемого вашей командой кода и поможет предотвратить многие [уязвимости нулевого дня](https://pvs-studio.ru/ru/blog/posts/0689/)\. 

## P\.S\.

Перед публикацией мы отправили статью разработчикам Сhocolatey, и они хорошо её приняли\. Ничего критичного нами найдено не было, но им, например, понравилась найденная нами ошибка, связанная с ключом "api\-key"\.