﻿# PVS\-Studio идёт в облака: Azure DevOps

Это вторая статья про использование статического анализатора PVS\-Studio в облачных CI\-системах, и на этот раз мы рассмотрим платформу Azure DevOps – облачное CI\\CD\-решение от компании Microsoft\. В качестве анализируемого проекта в этот раз рассмотрим ShareX\. 


> Для получения актуальной информации перейдите на обновляемую страницу документации " \[Использование в Azure DevOps\]\(https://pvs\-studio\.ru/ru/docs/manual/0053/\)"\\\.

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

Нам потребуется три компонента\. Первый \- статический анализатор PVS\-Studio\. Второй \- Azure DevOps, с которой мы будем интегрировать анализатор\. Третий \- проект, который мы будем проверять для демонстрации возможностей PVS\-Studio при работе в облаке\. Итак, приступим\.

[PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) \- статический анализатор кода для поиска ошибок и дефектов безопасности\. Выполняет анализ кода на языке C, C\+\+, C\# и Java\.

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

ShareX – бесплатное приложение, позволяющее захватывать и записывать любую часть экрана\. Проект написан на C\# и отлично подходит для демонстрации настройки запуска статического анализатора\. Исходный код проекта [доступен на GitHub](https://github.com/ShareX/ShareX)\.

Вывод команды cloc для проекта ShareX:

|Language|files|blank|comment|Code|
|---|---|---|---|---|
|C\\\#|696|20658|24423|102565|
|MSBuild script|11|1|77|5859|

Другими словами \- проект маленький, но вполне достаточный, чтобы продемонстрировать на наём работу PVS\-Studio в связке с облачной платформой\.

## Приступим к настройке

Для начала работы в Azure DevOps перейдем по [ссылке](https://azure.microsoft.com/en-us/services/devops/) и нажмём кнопку "Start free with GitHub"\.

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

Предоставим приложению Microsoft доступ к данным GitHub\-аккаунта\.

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

Для окончания регистрации придется создать аккаунт Microsoft\. 

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

После регистрации создадим проект:

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

Далее нам необходимо перейти в раздел "Pipelines" \- "Builds" и создать новый Build pipeline

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

На вопрос, где расположен наш код, ответим – GitHub\. 

![0670_Azure_DevOps_ru/image7.png](https://import.viva64.com/docx/blog/0670_Azure_DevOps_ru/image7.png)

Авторизуем приложение Azure Pipelines и выберем репозиторий с проектом, для которого мы будем настраивать запуск статического анализатора

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

В окне выбора шаблона выберем "Starter pipeline"\.

![0670_Azure_DevOps_ru/image9.png](https://import.viva64.com/docx/blog/0670_Azure_DevOps_ru/image9.png)

Запускать статический анализ кода проекта мы можем двумя путями: используя Microsoft\-hosted либо self\-hosted агенты\.

В первом варианте мы будем использовать Microsoft\-hosted агенты\. Такие агенты представляют собой обычные виртуальные машины, которые запускаются, когда мы запускаем наш pipeline, и удаляются после окончания задачи\. Использование таких агентов позволяет не тратить время на их поддержку и обновление, но накладывает некоторые ограничения, например – невозможность установки дополнительного программного обеспечения, которое используется для сборки проекта\.

Заменим предлагаемую нам конфигурацию по умолчанию на следующую для использования Microsoft\-hosted агентов:

```cpp
# Настройка триггеров запуска
# Запускаем для изменений только в master-ветке
trigger:
- master

# Так как установка произвольного ПО в виртуальные машины 
# запрещена, мы воспользуемся Docker-контейнером,
# запущенном в виртуальной машине с Windows Server 1803
pool:
  vmImage: 'win1803'
container: microsoft/dotnet-framework:4.7.2-sdk-windowsservercore-1803
           
steps:
# Скачиваем дистрибутив анализатора
- task: PowerShell@2
  inputs:
    targetType: 'inline'
    script: 'Invoke-WebRequest 
               -Uri https://files.pvs-studio.com/PVS-Studio_setup.exe 
               -OutFile PVS-Studio_setup.exe'
- task: CmdLine@2
  inputs:
    workingDirectory: $(System.DefaultWorkingDirectory)
    script: |
# Восстанавливаем проект и скачиваем зависимости
      nuget restore .\ShareX.sln
# Создаем директорию, куда будут сохранены файлы с отчетами анализатора
      md .\PVSTestResults
# Устанавливаем анализатор
      PVS-Studio_setup.exe /VERYSILENT /SUPPRESSMSGBOXES 
                     /NORESTART /COMPONENTS=Core
# Создаем файл с настройками и лицензионной информацией
      "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" 
          credentials 
    -u $(PVS_USERNAME) 
    -n $(PVS_KEY)

# Запускаем статический анализатор и конвертируем отчет в html. 
      "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" 
         -t .\ShareX.sln  
         -o .\PVSTestResults\ShareX.plog
      "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe" 
         -t html 
         -o .\PVSTestResults\ 
         .\PVSTestResults\ShareX.plog
    
# Сохраняем отчеты анализатора
- task: PublishBuildArtifacts@1
  inputs:
    pathToPublish: PVSTestResults
    artifactName: PVSTestResults
```

**Примечание:** согласно [документации](https://github.com/Microsoft/azure-pipelines-image-generation/blob/master/images/win/WindowsContainer1803-Readme.md), используемый контейнер должен быть закеширован в образе виртуальной машины, но на момент написания статьи это не работает и контейнер скачивается при каждом запуске задачи, что негативно сказывается на времени выполнении\.

Сохраним pipeline и создадим переменные, которые будут использоваться для создания файла лицензии\. Для этого откроем окно редактирования pipeline и в правом верхнем углу нажмем кнопку "Variables"\.

![0670_Azure_DevOps_ru/image10.png](https://import.viva64.com/docx/blog/0670_Azure_DevOps_ru/image10.png)

Добавим две переменные \- _PVS\_USERNAME_ и _PVS\_KEY_, содержащие имя пользователя и лицензионный ключ соответственно\. При создании переменной _PVS\_KEY_ не забываем отметить пункт "Keep this value secret" для шифрования значения переменной 2048\-битным RSA ключом, а также подавления вывода значения переменной в лог выполнения задачи\.

![0670_Azure_DevOps_ru/image11.png](https://import.viva64.com/docx/blog/0670_Azure_DevOps_ru/image11.png)

Сохраняем переменные и запускаем pipeline кнопкой "Run"\. 

Второй вариант запуска анализа – использовать self\-hosted агент\. Self\-hosted агенты \- это агенты, настраиваемые и управляемые нами самостоятельно\. Такие агенты дают больше возможностей для установки программного обеспечения, которое необходимо для сборки и тестирования нашего программного продукта\. 

Перед использованием таких агентов их необходимо настроить согласно [инструкции](https://docs.microsoft.com/ru-ru/azure/devops/pipelines/agents/v2-windows?view=azure-devops), а также установить и [настроить](https://pvs-studio.ru/ru/docs/manual/0046/) статический анализатор\.

Для запуска задачи на self\-hosted агенте заменим предлагаемую конфигурацию по умолчанию на следующую:

```cpp
# Настройка триггеров запуска
# Запускаем анализ для master-ветки
trigger:
- master

# Задача запускается на self-hosted агенте из пула 'MyPool' 
pool: 'MyPool'

steps:
- task: CmdLine@2
  inputs:
    workingDirectory: $(System.DefaultWorkingDirectory)
    script: |
# Восстанавливаем проект и скачиваем зависимости
      nuget restore .\ShareX.sln
# Создаем директорию, куда будут сохранены файлы с отчетами анализатора
      md .\PVSTestResults
# Запускаем статический анализатор и конвертируем отчет в html. 
      "C:\Program Files (x86)\PVS-Studio\PVS-Studio_Cmd.exe" 
         -t .\ShareX.sln
         -o .\PVSTestResults\ShareX.plog
      "C:\Program Files (x86)\PVS-Studio\PlogConverter.exe"
         -t html
         -o .\PVSTestResults\
         .\PVSTestResults\ShareX.plog
# Сохраняем отчеты анализатора
- task: PublishBuildArtifacts@1
  inputs:
    pathToPublish: PVSTestResults
    artifactName: PVSTestResults
```

После окончания выполнения задачи архив с отчетами анализатора можно скачать на вкладке "Summary", либо же мы можем воспользоваться расширением [Send Mail](https://marketplace.visualstudio.com/items?itemName=rvo.SendEmailTask&ssr=false), позволяющим настроить отправку электронной почты, или поискать более удобный для нас инструмент на [Marketplace](https://marketplace.visualstudio.com/search?target=AzureDevOps&category=Azure%20Pipelines&sortBy=Downloads)\.

![0670_Azure_DevOps_ru/image12.png](https://import.viva64.com/docx/blog/0670_Azure_DevOps_ru/image12.png)

## О результатах анализа

Теперь рассмотрим некоторые из ошибок, которые удалось обнаружить в проверяемом проекте \- ShareX\.

**Избыточные проверки**

Для разминки давайте начнём с простых недочётов в коде, а именно \- с избыточных проверок:

```cpp
private void PbThumbnail_MouseMove(object sender, MouseEventArgs e)
{
  ....
  IDataObject dataObject 
    = new DataObject(DataFormats.FileDrop,
                     new string[] { Task.Info.FilePath });

  if (dataObject != null)
  {
    Program.MainForm.AllowDrop = false;
    dragBoxFromMouseDown = Rectangle.Empty;
    pbThumbnail.DoDragDrop(dataObject, 
        DragDropEffects.Copy | DragDropEffects.Move);
    Program.MainForm.AllowDrop = true;
  }
  ....
}
```

**Предупреждение PVS\-Studio**: [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) \[CWE\-571\] Expression 'dataObject \!\= null' is always true\. TaskThumbnailPanel\.cs 415

Обратим внимание на проверку переменной _dataObject_ на _null_\. Для чего она здесь? _dataObject_ просто не может иметь значение _null_ в данном случае, так как инициализируется ссылкой на создаваемый объект\. В итоге имеем избыточную проверку\. Критично? Нет\. Выглядит лаконично? Нет\. Эту проверку явно лучше убрать, чтобы не загромождать код\.

Давайте взглянем ещё на один фрагмент кода, к которому можно предъявить аналогичные замечания:

```cpp
private static Image GetDIBImage(MemoryStream ms)
{
  ....
  try
  {
    ....
    return new Bitmap(bmp);
    ....
  }
  finally
  {
    if (gcHandle != IntPtr.Zero)
    {
      GCHandle.FromIntPtr(gcHandle).Free();
    }
  }
  ....
}
private static Image GetImageAlternative()
{
  ....
  using (MemoryStream ms = dataObject.GetData(format) as MemoryStream)
  {
    if (ms != null)
    {
      try
      {
        Image img = GetDIBImage(ms);
        if (img != null)
        {
          return img;
        }
      }
      catch (Exception e)
      {
        DebugHelper.WriteException(e);
      }
    }
  }
  ....
}
```

**Предупреждение PVS\-Studio**: [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) \[CWE\-571\] Expression 'img \!\= null' is always true\. ClipboardHelpers\.cs 289

В методе _GetImageAlternative_ опять выполняется проверка на то, что переменная _img_ не имеет значения _null_ сразу после того, как создали новый экземпляр класса _Bitmap_\. Отличие от предыдущего примера тут состоит в том, что для инициализации переменной _img_ мы используем не конструктор, а метод _GetDIBImage_\. Автор кода предполагает, что в этом методе может возникнуть исключительная ситуация, но объявляет только блоки _try_ и _finally_, опуская _catch_\. Следовательно, если произойдёт исключение, то вызывающий метод _\-_ _GetImageAlternative_ _\-_ не получит ссылку на объект типа _Bitmap,_ а будет вынужден обрабатывать исключение в собственном блоке _catch_\. В этом случае переменная _img_ не будет проинициализирована, и поток исполнения даже не дойдёт до проверки _img \!\= null_, а сразу попадёт в блок _catch_\. Следовательно, анализатор действительно указал на избыточную проверку\.

Рассмотрим следующий пример предупреждения с кодом [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/):

```cpp
private void btnCopyLink_Click(object sender, EventArgs e)
{
  ....
  if (lvClipboardFormats.SelectedItems.Count == 0)
  {
    url = lvClipboardFormats.Items[0].SubItems[1].Text;
  }
  else if (lvClipboardFormats.SelectedItems.Count > 0)
  {
    url = lvClipboardFormats.SelectedItems[0].SubItems[1].Text;
  }
  ....
}
```

**Предупреждение PVS\-Studio**: [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) \[CWE\-571\] Expression 'lvClipboardFormats\.SelectedItems\.Count \> 0' is always true\. AfterUploadForm\.cs 155

Присмотримся ко второму условному выражению\. Там мы проверяем значение свойства _Count_, доступного только для чтения\. Данное свойство показывает количество элементов в экземпляре коллекции _SelectedItems_\. Условие выполняется только в том случае, если свойство _Count_ больше нуля\. Всё было бы хорошо, да вот только во внешнем операторе _if_ уже выполняется проверка на то, что _Count_ равен нулю\. Экземпляр коллекции _SelectedItems_ не может иметь количество элементов меньше нуля, следовательно, _Count_ принимает значение либо равное нулю, либо больше нуля\. Раз мы уже выполнили проверку в первом операторе _if_ на то, что _Count_ равен нулю, и оно оказалось ложным, бессмысленно писать в ветке else ещё одну проверку на то, что _Count_ больше нуля\.

Заключительным примером ошибки с номером [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) будет следующий фрагмент кода:

```cpp
private void DrawCursorGraphics(Graphics g)
{
  ....
  int cursorOffsetX = 10, cursorOffsetY = 10, itemGap = 10, itemCount = 0;
  Size totalSize = Size.Empty;

  int magnifierPosition = 0;
  Bitmap magnifier = null;

  if (Options.ShowMagnifier)
  {
    if (itemCount > 0) totalSize.Height += itemGap;
    ....
  }
  ....
}
```

**Предупреждение PVS\-Studio**: [V3022](https://pvs-studio.ru/ru/docs/warnings/v3022/) Expression 'itemCount \> 0' is always false\. RegionCaptureForm\.cs 1100

Анализатор заметил, что условие _itemCount \> 0_ всегда будет ложным, так как чуть выше выполняется объявление и одновременное присваивание переменной _itemCount_ значения, равного нулю\. Вплоть до самого условия эта переменная нигде не используется и не изменяется, следовательно, анализатор сделал правильный вывод об условном выражении, значение которого всегда ложно\.

Что ж, давайте теперь рассмотрим что\-то действительно интересное\.

**Самый хороший способ понять баг — это визуализировать баг**

Как нам кажется, довольно интересная ошибка обнаружилась в этом месте:

```cpp
public static void Pixelate(Bitmap bmp, int pixelSize)
{
  ....
  float r = 0, g = 0, b = 0, a = 0;
  float weightedCount = 0;

  for (int y2 = y; y2 < yLimit; y2++)
  {
    for (int x2 = x; x2 < xLimit; x2++)
    {
      ColorBgra color = unsafeBitmap.GetPixel(x2, y2);

      float pixelWeight = color.Alpha / 255;

      r += color.Red * pixelWeight;
      g += color.Green * pixelWeight;
      b += color.Blue * pixelWeight;
      a += color.Alpha * pixelWeight;

      weightedCount += pixelWeight;
    }
  }
  ....
  ColorBgra averageColor = new ColorBgra((byte)(b / weightedCount),
    (byte)(g / weightedCount), (byte)(r / weightedCount),
    (byte)(a / pixelCount));
  ....
}
```

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

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

Данный метод принимает на вход два параметра: объекта типа _Bitmap_ и значение типа _int_, которое обозначает размер пикселизации\. Алгоритм работы достаточно прост:

1\) Разбиваем полученный на входе фрагмент изображения на квадраты со стороной, равной размеру пикселизации\. К примеру, если у нас размер пикселизации равен 15, то мы получим квадрат, содержащий 15x15\=225 пикселей\.

2\) Далее мы обходим каждый пиксель в этом квадрате и аккумулируем значения полей _Red_, _Green_, _Blue_ и _Alpha_ в промежуточные переменные, причём предварительно перемножая значение соответствующего цвета и значение альфа канала на переменную _pixelWeight_, получаемую путём деления значения _Alpha_ на 255 \(переменная _Alpha_ имеет тип _byte_\)\. Также при обходе пикселей мы суммируем значения, записанные в _pixelWeight,_ в переменную с именем _weightedCount_\. Фрагмент кода, выполняющий вышеописанные действия, выглядит следующим образом:

```cpp
ColorBgra color = unsafeBitmap.GetPixel(x2, y2);

float pixelWeight = color.Alpha / 255;

r += color.Red * pixelWeight;
g += color.Green * pixelWeight;
b += color.Blue * pixelWeight;
a += color.Alpha * pixelWeight;

weightedCount += pixelWeight;
```

Кстати, обратите внимание на то, что если значение переменной _Alpha_ равно нулю, то и _pixelWeight_ не будет добавлять к переменной _weightedCount_ никакого значения для данного пикселя\. Это нам понадобится в дальнейшем\.

3\) После того, как мы обошли все пиксели в текущем квадрате, мы можем составить общий "усреднённый" цвет для данного квадрата\. Код, выполняющий эти действия, выглядит следующим образом:

```cpp
ColorBgra averageColor = new ColorBgra((byte)(b / weightedCount),
    (byte)(g / weightedCount), (byte)(r / weightedCount),
    (byte)(a / pixelCount));
```

4\) Теперь, когда мы получили итоговый цвет и записали его в переменную _averageColor_, мы можем опять обойти каждый пиксель квадрата и присвоить ему значение из _averageColor_\.

5\) Возвращаемся к пункту 2 до тех пор, пока ещё остались необработанные квадраты\.

Ещё раз обратим внимание, что переменная _weightedCount_ не равна количеству всех пикселей в квадрате\. К примеру, если в изображении встречается абсолютно прозрачный пиксель \(значение ноль по альфа каналу\), то переменная _pixelWeight_ будет равна нулю для данного пикселя \(0 / 255 \= 0\), следовательно, этот пиксель не внесёт никакой вклад во формирование значения переменной _weightedCount_\. Это и логично \- нет смысла учитывать цвета абсолютно прозрачного пикселя\.

Всё кажется вполне разумным \- пикселизация должна работать правильно\. И она действительно работает правильно\. Вот только не для png изображений, которые имеют в своём составе пиксели со значениями в альфа канале меньше 255 и неравными нулю\. Обратите внимание на пикселизированную картинку снизу: 

![0670_Azure_DevOps_ru/image13.png](https://import.viva64.com/docx/blog/0670_Azure_DevOps_ru/image13.png)

Увидели пикселизацию? И мы нет\. Хорошо, теперь давайте раскроем эту небольшую интригу и поясним, где именно прячется баг в этом методе\. Ошибка закралась в строку вычисления значения переменной _pixelWeight_:

```cpp
float pixelWeight = color.Alpha / 255;
```

Дело в том, что автор кода, объявляя переменную _pixelWeight_ типом _float_, подразумевал, что при делении поля _Alpha_ на число 255 помимо нуля и единицы должны получаться дробные числа\. Здесь и кроется проблема, так как переменная _Alpha_ имеет тип _byte_, и при делении её на число 255 мы получаем целочисленное значение, и только потом оно будет неявно приведено к типу _float_, следовательно, происходит потеря дробной части\.

Невозможность провести пикселизацию для изображений формата png, которые имеют некоторую степень прозрачности, легко объяснить\. Так как значения альфа канала у данных пикселей лежит в диапазоне 0 < Alpha < 255, то, при делении переменной _Alpha_ на число 255, мы всегда будем получать 0\. Следовательно, значения переменных _pixelWeight_, _r_, _g_, _b_, _a_, _weightedCount_ тоже всегда будут равны нулю\. Как итог, наш усреднённый цвет _averageColor_ будет с нулевыми значениями по всем каналам: красный \- 0, синий \- 0, зелёный \- 0, альфа \- 0\. Закрашивая квадрат в такой цвет, мы не изменяем исходный цвет пикселей, так как _averageColor_ является абсолютно прозрачным\. Для того, чтобы исправить эту ошибку, необходимо просто явно привести поле _Alpha_ к типу _float_\. Исправленная строка кода может выглядеть следующим образом:

```cpp
float pixelWeight = (float)color.Alpha / 255;
```

И пора привести сообщение, которое выдавал PVS\-Studio на ещё некорректный код:

**Предупреждение PVS\-Studio**: [V3041](https://pvs-studio.ru/ru/docs/warnings/v3041/) \[CWE\-682\] The expression was implicitly cast from 'int' type to 'float' type\. Consider utilizing an explicit type cast to avoid the loss of a fractional part\. An example: double A \= \(double\)\(X\) / Y;\. ImageHelpers\.cs 1119

И для сравнения приведём скриншот действительно пикселизированного изображения, полученного на исправленной версии приложения:

![0670_Azure_DevOps_ru/image14.png](https://import.viva64.com/docx/blog/0670_Azure_DevOps_ru/image14.png)

**Потенциально возможное исключение NullReferenceException**

```cpp
public static bool AddMetadata(Image img, int id, string text)
{
  ....
  pi.Value = bytesText;

  if (pi != null)
  {
    img.SetPropertyItem(pi);
    return true;
  }
  ....
}
```

**Предупреждение PVS\-Studio:** [V3095](https://pvs-studio.ru/ru/docs/warnings/v3095/) \[CWE\-476\] The 'pi' object was used before it was verified against null\. Check lines: 801, 803\. ImageHelpers\.cs 801

Данный фрагмент кода показывает, что его автор ожидал, что переменная _pi_ может иметь значение _null_, именно поэтому перед тем, как вызывать метод _SetPropertyItem_, выполняется проверка _pi \!\= null_\. Странно, что перед этой проверкой происходит присваивание свойству _pi\.Value_ массива байт, ведь если _pi_ будет равно _null_, то будет выброшено исключение типа _NullReferenceException_\.

Аналогичная ситуация была замечена и в другом месте:

```cpp
private static void Task_TaskCompleted(WorkerTask task)
{
  ....
  task.KeepImage = false;

  if (task != null)
  {
    if (task.RequestSettingUpdate)
    {
      Program.MainForm.UpdateCheckStates();
    }
    ....
  }
  ....
}
```

**Предупреждение PVS\-Studio:** [V3095](https://pvs-studio.ru/ru/docs/warnings/v3095/) \[CWE\-476\] The 'task' object was used before it was verified against null\. Check lines: 268, 270\. TaskManager\.cs 268

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

**Предупреждение PVS\-Studio:** [V3095](https://pvs-studio.ru/ru/docs/warnings/v3095/) \[CWE\-476\] The 'Config\.PhotobucketAccountInfo' object was used before it was verified against null\. Check lines: 216, 219\. UploadersConfigForm\.cs 216

**Одно и тоже возвращаемое значение**

Подозрительный фрагмент кода был обнаружен в методе _EvalWindows_ класса _WindowsList_, который при любых обстоятельствах возвращает _true_:

```cpp
public class WindowsList
{
  public List<IntPtr> IgnoreWindows { get; set; }
  ....
  public WindowsList()
  {
    IgnoreWindows = new List<IntPtr>();
  }

  public WindowsList(IntPtr ignoreWindow) : this()
  {
    IgnoreWindows.Add(ignoreWindow);
  }
  ....
  private bool EvalWindows(IntPtr hWnd, IntPtr lParam)
  {
    if (IgnoreWindows.Any(window => hWnd == window))
    {
      return true;  // <=
    }

    windows.Add(new WindowInfo(hWnd));

    return true;  // <=
  }
}
```

**Предупреждение PVS\-Studio:** [V3009](https://pvs-studio.ru/ru/docs/warnings/v3009/) It's odd that this method always returns one and the same value of 'true'\. WindowsList\.cs 82

Кажется логичным, что если в списке с названием _IgnoreWindows_ был бы найден указатель с таким же значением, как и у _hWnd_, то метод должен был вернуть значение _false_\. 

Список _IgnoreWindows_ может заполняться либо при вызове конструктора _WindowsList\(IntPtr ignoreWindow\)_, либо напрямую через доступ к свойству, так как оно публичное\. Так или иначе, если верить Visual Studio, на данный момент в коде этот список никак не заполняется\. Это ещё одно странное место этого метода\.

**Примечание**\. После небольшой переписки с одним из разработчиков проекта ShareX, было установлено, что метод EvalWindows, всегда возвращающий значение true, преднамеренно был написан таким образом\.

**Небезопасный вызов обработчиков событий**

```cpp
protected void OnNewsLoaded()
{
  if (NewsLoaded != null)
  {
    NewsLoaded(this, EventArgs.Empty);
  }
}
```

**Предупреждение PVS\-Studio:** [V3083](https://pvs-studio.ru/ru/docs/warnings/v3083/) \[CWE\-367\] Unsafe invocation of event 'NewsLoaded', NullReferenceException is possible\. Consider assigning event to a local variable before invoking it\. NewsListControl\.cs 111

В данном случае у нас может произойти следующая неприятная ситуация: после проверки переменной _NewsLoaded_ на неравенство _null_ метод, выполняющий обработку события, может быть отписан, к примеру, в другом потоке, и, когда мы попадём в тело условного оператора _if_, переменная _NewsLoaded_ уже будет равна _null_\. Попытка вызвать подписчиков у события _NewsLoaded_, которое имеет значение _null_, приведёт к возникновению исключения _NullReferenceException_\. Гораздо безопаснее воспользоваться null\-условным оператором и переписать приведённый выше код следующим образом:

```cpp
protected void OnNewsLoaded()
{
  NewsLoaded?.Invoke(this, EventArgs.Empty);
}
```

Анализатор указал на ещё **68** аналогичных мест\. Описывать здесь их не будем \- паттерн вызова события в них подобный\.

**Возвращаем null из ToString**

Не так давно из одной [интересной статьи](https://pvs-studio.ru/ru/blog/posts/csharp/0656/) моего коллеги я узнал, что Microsoft не рекомендует возвращать _null_ из переопределяемого метода _ToString_\. PVS\-Studio хорошо осведомлён об этом:

```cpp
public override string ToString()
{
  lock (loggerLock)
  {
    if (sbMessages != null && sbMessages.Length > 0)
    {
      return sbMessages.ToString();
    }

    return null;
   }
 }
```

**Предупреждение PVS\-Studio:** [V3108](https://pvs-studio.ru/ru/docs/warnings/v3108/) It is not recommended to return 'null' from 'ToSting\(\)' method\. Logger\.cs 167

**Зачем присваивать, если не используешь?**

```cpp
public SeafileCheckAccInfoResponse GetAccountInfo()
{
  string url = URLHelpers.FixPrefix(APIURL);
  url = URLHelpers.CombineURL(APIURL, "account/info/?format=json");
....
}
```

**Предупреждение PVS\-Studio:** [V3008](https://pvs-studio.ru/ru/docs/warnings/v3008/) The 'url' variable is assigned values twice successively\. Perhaps this is a mistake\. Check lines: 197, 196\. Seafile\.cs 197

Как видно из примера, при объявлении переменной _url_ ей присваивается некоторое значение, возвращаемое из метода _FixPrefix_\. В последующей строке мы "перетираем" полученное значение, даже нигде его не использовав\. Получаем что\-то похожее на "мёртвый код" \- работу выполняет, на итоговый результат никак не влияет\. Данная ошибка, скорее всего, является результатом копипаста, так как подобные фрагменты кода встречаются ещё в 9 методах\. Для примера, приведём два метода с аналогичной первой строкой:

```cpp
public bool CheckAuthToken()
{
  string url = URLHelpers.FixPrefix(APIURL);
  url = URLHelpers.CombineURL(APIURL, "auth/ping/?format=json");
  ....
}
....
public bool CheckAPIURL()
{
  string url = URLHelpers.FixPrefix(APIURL);
  url = URLHelpers.CombineURL(APIURL, "ping/?format=json");
  ....
}
```

## Итого

Как мы видим, сложность настройки автоматической проверки анализатором не зависит от выбранной CI\-системы – буквально за 15 минут и несколько кликов мышкой мы настроили проверку кода нашего проекта статическим анализатором\. 

В заключение предлагаем и вам [загрузить и попробовать анализатор](https://pvs-studio.ru/ru/pvs-studio/download/) на своих проектах\.