PVS-Studio зараз у Chocolatey: праверка Chocolatey з-пад Azure DevOps

PVS-Studio зараз у Chocolatey: праверка Chocolatey з-пад Azure DevOps
Мы працягваем рабіць выкарыстанне PVS-Studio зручней. Цяпер наш аналізатар даступны ў Chocolatey, пакетным мэнэджэры для Windows. Мы лічым, што гэта аблегчыць разгортванне PVS-Studio, у прыватнасці, у хмарных сэрвісах. Каб не ісці далёка, праверым зыходны код усё таго ж Chocolatey. У якасці CI сістэмы выступіць Azure DevOps.

Вось спіс іншых нашых артыкулаў на тэму інтэграцыі з хмарнымі сістэмамі:

Раю звярнуць увагу на першы артыкул пра інтэграцыю з Azure DevOps, бо ў дадзеным выпадку некаторыя моманты апушчаны, каб не дубліравацца.

Такім чынам, героі дадзенага артыкула:

PVS-студыя - інструмент статычнага аналізу кода, прызначаны для выяўлення памылак і патэнцыйных уразлівасцяў у праграмах, напісаных на мовах З, C++, C# і Java. Працуе ў 64-бітных сістэмах на Windows, Linux і macOS, і можа аналізаваць код, прызначаны для 32-бітных, 64-бітных і ўбудавальных ARM платформаў. Калі вы ўпершыню будзеце спрабаваць статычны аналіз кода для праверкі сваіх праектаў, то рэкамендуемы азнаёміцца ​​са артыкулам аб тым, як хутка паглядзець самыя цікавыя папярэджанні PVS-Studio і ацаніць магчымасці гэтай прылады.

Azure DevOps - Набор хмарных сэрвісаў, сумесна якія ахопліваюць увесь працэс распрацоўкі. У склад дадзенай платформы ўваходзяць такія прылады, як Azure Pipelines, Azure Boards, Azure Artifacts, Azure Repos, Azure Test Plans, якія дазваляюць паскорыць працэс стварэння праграмнага забеспячэння і павысіць яго якасць.

шакаладным - пакетны мэнэджар для Windows з адчыненым зыходным кодам. Мэта праекта - аўтаматызаваць увесь жыццёвы цыкл праграмнага забеспячэння ад усталёўкі да абнаўлення і выдаленні ў аперацыйных сістэмах Windows.

Аб выкарыстанні Chocolatey

Паглядзець, як усталяваць сам пакетны мэнэджар, вы можаце па гэтай спасылцы. Поўная дакументацыя па ўстаноўцы аналізатара даступна па спасылцы у раздзеле "Усталяванне з выкарыстаннем пакетнага мэнэджара Chocolatey". Сцісла паўтару некаторыя моманты адтуль.

Каманда для ўстаноўкі апошняй версіі аналізатара:

choco install pvs-studio

Каманда ўстаноўкі канкрэтнай версіі пакета PVS-Studio:

choco install pvs-studio --version=7.05.35617.2075

Па змаўчанні усталёўваецца толькі ядро ​​аналізатара - кампанент Core. Усе астатнія сцягі (Standalone, JavaCore, IDEA, MSVS2010, MSVS2012, MSVS2013, MSVS2015, MSVS2017, MSVS2019) можна перадаць пры дапамозе -package-parameters.

Прыклад каманды, якая ўсталюе аналізатар з убудовай для Visual Studio 2019:

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

Цяпер паглядзім на прыклад зручнага выкарыстання аналізатара пад Azure DevOps.

Настройка

Нагадваю, што пра такія моманты, як рэгістрацыя ўліковага запісу, стварэнне Build Pipeline і сінхранізацыя ўліковага запісу з праектам, які ляжыць у рэпазітары на GitHub, ёсць асобная артыкул. Наша ж настройка адразу пачнецца з напісання канфігурацыйнага файла.

Для пачатку наладзім трыгер запуску, паказаўшы, што вырабляем запуск толькі для змен у майстар галінцы:

trigger:
- master

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

pool:
  vmImage: 'windows-latest'

Пяройдзем да цела канфігурацыйнага файла (блок крокі). Нягледзячы на ​​тое, што ў віртуальную машыну нельга ўсталяваць адвольнае ПЗ, я не стаў дадаваць Docker кантэйнер. Мы можам дадаць Chocolatey як пашырэнне для Azure DevOps. Для гэтага пяройдзем па спасылцы. Ціснем Атрымаць бясплатна. Далей, калі вы ўжо аўтарызаваны, проста выбіраем свой уліковы запіс, а калі не, то прарабляем усё тое ж самае пасля аўтарызацыі.

PVS-Studio зараз у Chocolatey: праверка Chocolatey з-пад Azure DevOps

Тут трэба абраць, куды мы дадамо пашырэнне, і націснуць кнопку Усталёўваць.

PVS-Studio зараз у Chocolatey: праверка Chocolatey з-пад Azure DevOps

Пасля шчаснай усталёўкі націснем Proceed to organization:

PVS-Studio зараз у Chocolatey: праверка Chocolatey з-пад Azure DevOps

Цяпер можна ўбачыць шаблон для задачы Chocolatey у акне задачы пры рэдагаванні канфігурацыйнага файла azure-pipelines.yml:

PVS-Studio зараз у Chocolatey: праверка Chocolatey з-пад Azure DevOps

Націснем на Chocolatey і ўбачым спіс палёў:

PVS-Studio зараз у Chocolatey: праверка Chocolatey з-пад Azure DevOps

Тут нам трэба абраць ўсталёўваць у поле з камандамі. У Nuspec File Name пакажам назву патрэбнага пакета - pvs-studio. Калі не пазначыць версію, усталюецца апошняя, што нас цалкам задавальняе. Націснем на кнопку дадаваць і ўбачым якая сфармавалася задачу ў файле канфігурацыі.

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

Далей пяройдзем да асноўнай часткі нашага файла:

- task: CmdLine@2
  inputs:
    script: 

Цяпер нам трэба стварыць файл з ліцэнзіяй аналізатара. Тут PVSNAME и PVSKEY – назвы зменных, значэнні якіх мы паказваем у настройках. Яны будуць захоўваць лагін і ліцэнзійны ключ PVS-Studio. Каб усталяваць іх значэння, адкрыем меню Variables->New variable. Створым зменныя PVSNAME для лагіна і PVSKEY для ключа аналізатара. Не забудзьцеся паставіць галачку Keep this value secret для PVSKEY. Код каманды:

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

Збяром праект пры дапамозе bat-файла, які ляжыць у рэпазітары:

сall build.bat

Створым тэчку, дзе будуць ляжаць файлы з вынікамі працы аналізатара:

сall mkdir PVSTestResults

Запусцім аналіз праекта:

сall "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe" 
–t .srcchocolatey.sln –o .PVSTestResultsChoco.plog 

Канвертуем нашу справаздачу ў фармат html пры дапамозе ўтыліты PlogСonverter:

сall "C:Program Files (x86)PVS-StudioPlogConverter.exe" 
–t html –o PVSTestResults .PVSTestResultsChoco.plog

Цяпер трэба стварыць заданне для таго, каб можна было выгрузіць справаздачу.

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

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

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-StudioPVS-Studio_Cmd.exe" 
      credentials –u $(PVSNAME) –n $(PVSKEY)
      call build.bat
      call mkdir PVSTestResults
      call "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe" 
      –t .srcchocolatey.sln –o .PVSTestResultsChoco.plog
      call "C:Program Files (x86)PVS-StudioPlogConverter.exe" 
      –t html –o .PVSTestResults .PVSTestResultsChoco.plog

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

Націснем Save->Save->Run для запуску задачы. Выгрузім справаздачу, зайшоўшы ва ўкладку задачы.

PVS-Studio зараз у Chocolatey: праверка Chocolatey з-пад Azure DevOps

Праект Chocolatey змяшчае ўсяго 37615 радкоў C# кода. Разгледзім некаторыя са знойдзеных памылак.

Вынікі праверкі

Папярэджанне N1

Папярэджанне аналізатара: V3005 'Provider' variable з'яўляецца itself. CrytpoHashProviderSpecs.cs 38

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

Аналізатар выявіў прысвойванне зменнай самой сабе, што не мае сэнсу. Хутчэй за ўсё, на месцы адной з гэтых зменных павінна быць нейкая іншая. Ну ці гэта памылка друку, і лішняе прысвойванне можна проста выдаліць.

Папярэджанне N2

Папярэджанне аналізатара: V3093 [CWE-480] The '&' operator evaluates both operands. Perhaps a short-circuit '&&' абаронца павінен быць выкарыстаны. Platform.cs 64

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;
  }
}

Адрозненне аператара & ад аператара && заключаецца ў тым, што калі левая частка выразы ілжывы, то ўсё роўна будзе вылічана правая частка, што ў дадзеным выпадку мае на ўвазе лішнія выклікі метаду system.directory_exists.

У разгледжаным фрагменце гэта дробны недахоп. Так, дадзеную ўмову можна аптымізаваць, замяніўшы аператар & на аператар &&, але, з практычнага пункта гледжання, гэта ні на што не ўплывае. Аднак, у іншых выпадках блытаніна паміж & і && можа выклікаць сур'ёзныя праблемы, калі правая частка выразы будзе працаваць з некарэктнымі/недапушчальнымі значэння. Напрыклад, у нашай калекцыі памылак, выяўленых з дапамогай дыягностыкі V3093, ёсць вось такі выпадак:

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

Нават калі індэкс k некарэктны, ён будзе выкарыстоўвацца для доступу да элемента масіва. У выніку будзе згенеравана выключэнне IndexOutOfRangeException.

Папярэджанні N3, N4

Папярэджанне аналізатара: V3022 [CWE-571] Expression 'shortPrompt' is always true. InteractivePrompt.cs 101
Папярэджанне аналізатара: V3022 [CWE-571] Expression 'shortPrompt' is always true. InteractivePrompt.cs 105

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, якое заўсёды праўда, а значыць выканаецца радок 3. Калі ж умова 1 апынецца ілжывым, то мы пяройдзем на радок, пазначаную лічбай 4, умова ў якой таксама заўсёды праўда, а значыць, выканаецца радок 5. Такім чынам, умовы, пазначаныя каментаром 0, ніколі не будуць выкананы, што можа з'яўляцца не зусім той логікай працы, на якую разлічваў праграміст.

Папярэджанне N5

Папярэджанне аналізатара: V3123 [CWE-783] Вырашыце, што '?:' абаронца працуе ў розных выпадках, калі ён быў выяўлены. Гэтая priorita з'яўляецца нізкай, а priority of other operators in its condition. Options.cs 1019

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);
  }
}

Дыягностыка спрацавала на радок:

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

Бо зменная j на некалькі радкоў вышэй ініцыялізуецца нулём, тэрнарны аператар верне значэнне ілжывы. З-за дадзенай умовы, цела цыклу выканаецца толькі адзін раз. Мне падаецца, што дадзены фрагмент кода працуе зусім не так, як задумваў праграміст.

Папярэджанне N6

Папярэджанне аналізатара: V3022 [CWE-571] Expression 'installedPackageVersions.Count != 1' is always true. NugetService.cs 1405

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, якое заўсёды будзе праўда. Часта такое папярэджанне паказвае на лагічную памылку ў кодзе, а ў астатніх выпадках проста на залішнюю праверку.

Папярэджанне N7

Папярэджанне аналізатара: V3001 Ёсць падобныя sub-expressions 'commandArguments.contains("-apikey")' у левай і правы '||' operator. ArgumentsUtility.cs 42

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 яшчэ парай спосабаў. Аналагічна параметрам вышэй, магу прапанаваць такія варыянты:

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

Copy-paste памылкі маюць вялікі шанец рана ці позна з'явіцца ў любым праекце з вялікай колькасцю зыходнага кода, і адно з лепшых сродкаў барацьбы з імі - статычны аналіз.

PS І як заўсёды, гэтая памылка імкнецца з'явіцца ў канцы шматрадковай умовы :). Глядзіце публікацыю "Эфект апошняга радка".

Папярэджанне N8

Папярэджанне аналізатара: V3095 [CWE-476] 'installedPackage' аб'ект быў выкарыстаны перад тым, што ён быў зняволены да null. Check lines: 910, 917. NugetService.cs 910

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 выкарыстоўваецца, а потым правяраецца на нуля. Дадзеная дыягностыка кажа нам аб адной з двух праблем у праграме: альбо installedPackage ніколі не роўны нуля, Што сумнеўна, і тады праверка залішняя, альбо мы патэнцыйна можам атрымаць сур'ёзную памылку ў кодзе – спробу доступу па нулявой спасылцы.

Заключэнне

Вось мы і прарабілі яшчэ адзін маленькі крок - зараз карыстацца PVS-Studio стала яшчэ прасцей і зручней. Таксама хачу сказаць, што Chocolatey - добры пакетны мэнэджар з невялікай колькасцю памылак у кодзе, якіх магло б стаць яшчэ менш пры выкарыстанні PVS-Studio.

Запрашаем спампаваць і паспрабаваць PVS-Studio. Рэгулярнае выкарыстанне статычнага аналізатара павысіць якасць і надзейнасць распрацоўванага вашай камандай кода і дапаможа прадухіліць шматлікія уразлівасці нулявога дня.

PS

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

PVS-Studio зараз у Chocolatey: праверка Chocolatey з-пад Azure DevOps

Калі хочаце падзяліцца гэтым артыкулам з англамоўнай аўдыторыяй, то прашу выкарыстаць спасылку на пераклад: Vladislav Stolyarov. PVS-студыя ў chocolatey: Checking Chocolatey пад Azure DevOps.

Крыніца: habr.com

Дадаць каментар