PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
PVS-Studio անալիզատորի օգտագործման ամենաարդի սցենարներից մեկը CI համակարգերի հետ դրա ինտեգրումն է: Եվ չնայած PVS-Studio նախագծի վերլուծությունը գրեթե ցանկացած շարունակական ինտեգրացիոն համակարգից կարող է ներկառուցվել ընդամենը մի քանի հրամանների մեջ, մենք շարունակում ենք այս գործընթացը դարձնել էլ ավելի հարմար: PVS-Studio-ն այժմ ունի աջակցություն անալիզատորի ելքը TeamCity - TeamCity Inspections Type-ի ձևաչափի փոխակերպելու համար: Տեսնենք, թե ինչպես է այն աշխատում:

Տեղեկություններ օգտագործվող ծրագրաշարի մասին

PVS- ստուդիա — C, C++, C# և Java կոդի ստատիկ անալիզատոր, որը նախատեսված է հեշտացնելու տարբեր տեսակի սխալներ գտնելու և ուղղելու խնդիրը: Անալիզատորը կարող է օգտագործվել Windows-ի, Linux-ի և macOS-ի վրա: Այս հոդվածում մենք ակտիվորեն կօգտագործենք ոչ միայն ինքնին անալիզատորը, այլև դրա բաշխման որոշ կոմունալ ծառայություններ:

CLMonitor — մոնիտորինգի սերվեր է, որը վերահսկում է կոմպիլյատորների գործարկումները: Այն պետք է գործարկվի անմիջապես նախքան ձեր նախագիծը կառուցելը: Հետախուզման ռեժիմում սերվերը կկանգնեցնի բոլոր աջակցվող կոմպիլյատորների գործարկումները: Հարկ է նշել, որ այս օգտակար ծրագիրը կարող է օգտագործվել միայն C/C++ նախագծերը վերլուծելու համար:

PlogConverter – անալիզատորի հաշվետվությունները տարբեր ձևաչափերի փոխակերպելու օգտակար ծրագիր:

Տեղեկություններ ուսումնասիրվող ծրագրի մասին

Փորձենք այս ֆունկցիոնալությունը գործնական օրինակով. եկեք վերլուծենք OpenRCT2 նախագիծը:

OpenRCT2 - RollerCoaster Tycoon 2 (RCT2) խաղի բաց իրականացում, ընդլայնելով այն նոր գործառույթներով և շտկելով սխալները: Խաղը պտտվում է զվարճանքի պարկի կառուցման և պահպանման շուրջ, որը պարունակում է զբոսանքներ, խանութներ և հարմարություններ: Խաղացողը պետք է փորձի շահույթ ստանալ և պահպանել այգու բարի համբավը՝ միաժամանակ ուրախացնելով հյուրերին: OpenRCT2-ը թույլ է տալիս խաղալ և՛ սցենարներում, և՛ ավազատուփերում: Սցենարները խաղացողից պահանջում են որոշակի առաջադրանք կատարել սահմանված ժամկետում, մինչդեռ Sandbox-ը խաղացողին թույլ է տալիս կառուցել ավելի ճկուն այգի՝ առանց որևէ սահմանափակման կամ ֆինանսների:

հարմարեցում

Ժամանակ խնայելու համար ես հավանաբար բաց կթողնեմ տեղադրման գործընթացը և կսկսեմ այն ​​պահից, երբ իմ համակարգչում աշխատում է TeamCity սերվերը: Մենք պետք է գնանք. Մտնելուց հետո մեզ կդիմավորեն.

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
Սեղմեք Ստեղծել նախագիծ կոճակը: Հաջորդը, ընտրեք Ձեռքով և լրացրեք դաշտերը:

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
Կոճակը սեղմելուց հետո Ստեղծել, մեզ դիմավորում է կարգավորումներով պատուհան։

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
Եկեք սեղմենք Ստեղծեք կառուցման կոնֆիգուրացիա.

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
Լրացրեք դաշտերը և սեղմեք Ստեղծել. Մենք տեսնում ենք պատուհան, որում խնդրում ենք ընտրել տարբերակի կառավարման համակարգ: Քանի որ աղբյուրներն արդեն տեղակայված են տեղում, սեղմեք Բացթողել.

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
Ի վերջո, մենք անցնում ենք նախագծի կարգավորումներին:

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
Եկեք ավելացնենք հավաքման քայլերը, սա անելու համար սեղմեք. Կառուցման քայլեր -> Ավելացնել կառուցման քայլ.

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
Այստեղ մենք ընտրում ենք.

  • Runner տեսակ -> Հրամանի տող
  • Run -> Custom Script

Քանի որ մենք վերլուծություն կկատարենք նախագծի կազմման ժամանակ, հավաքումը և վերլուծությունը պետք է լինեն մեկ քայլ, ուստի լրացրեք դաշտը Մաքսային սց:

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
Հետագայում մենք կանդրադառնանք առանձին քայլերին: Կարևոր է, որ անալիզատորի բեռնումը, նախագիծը հավաքելը, այն վերլուծելը, զեկույցը թողարկելը և դրա ձևաչափումը պահանջում են ընդամենը տասնմեկ տող կոդ:

Վերջին բանը, որ մենք պետք է անենք, շրջակա միջավայրի փոփոխականները սահմանելն է, որոնք ես նախանշել եմ դրանց ընթեռնելիությունը բարելավելու որոշ ուղիներ: Դա անելու համար եկեք շարժվենք. Պարամետրեր -> Ավելացնել նոր պարամետր և ավելացնել երեք փոփոխական.

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
Ընդամենը պետք է սեղմել կոճակը Վազում վերին աջ անկյունում։ Մինչ նախագիծը հավաքվում և վերլուծվում է, ես ձեզ կպատմեմ սցենարի մասին:

Ուղիղ սցենար

Նախ, մենք պետք է ներբեռնենք PVS-Studio-ի վերջին բաշխումը: Դրա համար մենք օգտագործում ենք Chocolatey փաթեթի կառավարիչը: Նրանց համար, ովքեր ցանկանում են ավելին իմանալ այս մասին, կա համապատասխան հոդված:

choco install pvs-studio -y

Հաջորդը, եկեք գործարկենք CLMonitor նախագծի կառուցման հետագծման օգտակար ծրագիրը:

%CLmon% monitor –-attach

Այնուհետև մենք նախագիծը կկառուցենք որպես շրջակա միջավայրի փոփոխական MSB MSBuild-ի այն տարբերակն է, որը ես պետք է կառուցեմ

%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable

Եկեք մուտքագրենք PVS-Studio-ի մուտքի և լիցենզիայի բանալին.

%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%

Կառուցումն ավարտվելուց հետո նորից գործարկեք CLMonitor-ը՝ նախապես մշակված ֆայլեր և ստատիկ վերլուծություն ստեղծելու համար.

%CLmon% analyze -l "c:ptest.plog"

Այնուհետև մենք կօգտագործենք մեկ այլ օգտակար ծառայություն մեր բաշխումից: PlogConverter-ը հաշվետվությունը ստանդարտ ձևաչափից վերածում է TeamCity-ի հատուկ ձևաչափի: Դրա շնորհիվ մենք կկարողանանք դիտել այն անմիջապես կառուցման պատուհանում:

%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"

Վերջին քայլը ձևաչափված զեկույցի ցուցադրումն է stdout, որտեղ այն կվերցվի TeamCity վերլուծիչի կողմից:

type "C:tempptest.plog_TeamCity.txt"

Ամբողջական սցենարի կոդը.

choco install pvs-studio -y
%CLmon% monitor --attach
set platform=x64
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%
%CLmon% analyze -l "c:ptest.plog"
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"
type "C:tempptest.plog_TeamCity.txt"

Միևնույն ժամանակ, նախագծի հավաքումն ու վերլուծությունը հաջողությամբ ավարտվել են, մենք կարող ենք անցնել ներդիր ծրագրեր և համոզվեք դրանում:

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
Հիմա եկեք սեղմենք Ստուգումներ Ընդամենըգնալ անալիզատորի հաշվետվությունը դիտելու համար.

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
Զգուշացումները խմբավորված են ըստ ախտորոշիչ կանոնների համարների: Կոդի միջով նավարկելու համար հարկավոր է սեղմել նախազգուշացումով տողի համարը: Սեղմելով վերևի աջ անկյունում գտնվող հարցական նշանի վրա, կբացվի փաստաթղթերով նոր ներդիր: Կարող եք նաև նավարկել կոդի միջով՝ սեղմելով անալիզատորի նախազգուշացումով տողի համարը: Օգտագործելիս հնարավոր է նավարկություն հեռավոր համակարգչից SourceTreeRoot մարկեր. Յուրաքանչյուր ոք, ով հետաքրքրված է անալիզատորի աշխատանքի այս եղանակով, կարող է ծանոթանալ համապատասխան բաժնին փաստաթղթավորում.

Դիտելով անալիզատորի արդյունքները

Այժմ, երբ մենք ավարտեցինք կառուցվածքի տեղակայումն ու կազմաձևումը, եկեք տեսնենք մի քանի հետաքրքիր նախազգուշացումներ, որոնք հայտնաբերվել են մեր դիտարկած նախագծում:

Զգուշացում N1

V773 [CWE-401] Բացառությունը նետվեց առանց «արդյունք» ցուցիչը բաց թողնելու: Հնարավոր է հիշողության արտահոսք։ libopenrct2 ObjectFactory.cpp 443

Object* CreateObjectFromJson(....)
{
  Object* result = nullptr;
  ....
  result = CreateObject(entry);
  ....
  if (readContext.WasError())
  {
    throw std::runtime_error("Object has errors");
  }
  ....
}

Object* CreateObject(const rct_object_entry& entry)
{
  Object* result;
  switch (entry.GetType())
  {
    case OBJECT_TYPE_RIDE:
      result = new RideObject(entry);
      break;
    case OBJECT_TYPE_SMALL_SCENERY:
      result = new SmallSceneryObject(entry);
      break;
    case OBJECT_TYPE_LARGE_SCENERY:
      result = new LargeSceneryObject(entry);
      break;
    ....
    default:
      throw std::runtime_error("Invalid object type");
  }
  return result;
}

Անալիզատորը նկատել է սխալ, որը դինամիկ կերպով հիշողությունը տեղաբաշխելուց հետո CreateObject, երբ բացառություն է տեղի ունենում, հիշողությունը չի մաքրվում, և հիշողության արտահոսք է տեղի ունենում:

Զգուշացում N2

V501 Կան միանման ենթարտահայտություններ «(1ULL << WIDX_MONTH_BOX)» «|»-ի ձախ և աջ կողմում: օպերատոր. libopenrct2ui Cheats.cpp 487

static uint64_t window_cheats_page_enabled_widgets[] = 
{
  MAIN_CHEAT_ENABLED_WIDGETS |
  (1ULL << WIDX_NO_MONEY) |
  (1ULL << WIDX_ADD_SET_MONEY_GROUP) |
  (1ULL << WIDX_MONEY_SPINNER) |
  (1ULL << WIDX_MONEY_SPINNER_INCREMENT) |
  (1ULL << WIDX_MONEY_SPINNER_DECREMENT) |
  (1ULL << WIDX_ADD_MONEY) |
  (1ULL << WIDX_SET_MONEY) |
  (1ULL << WIDX_CLEAR_LOAN) |
  (1ULL << WIDX_DATE_SET) |
  (1ULL << WIDX_MONTH_BOX) |  // <=
  (1ULL << WIDX_MONTH_UP) |
  (1ULL << WIDX_MONTH_DOWN) |
  (1ULL << WIDX_YEAR_BOX) |
  (1ULL << WIDX_YEAR_UP) |
  (1ULL << WIDX_YEAR_DOWN) |
  (1ULL << WIDX_DAY_BOX) |
  (1ULL << WIDX_DAY_UP) |
  (1ULL << WIDX_DAY_DOWN) |
  (1ULL << WIDX_MONTH_BOX) |  // <=
  (1ULL << WIDX_DATE_GROUP) |
  (1ULL << WIDX_DATE_RESET),
  ....
};

Քիչ մարդիկ, բացի ստատիկ անալիզատորից, կարող էին անցնել ուշադրության այս թեստը: Այս copy-paste օրինակը լավ է հենց այս պատճառով:

Զգուշացումներ N3

V703 Տարօրինակ է, որ ստացված «RCT12BannerElement» դասի «դրոշներ» դաշտը վերագրանցում է «RCT12TileElementBase» բազային դասի դաշտը: Ստուգեք տողերը՝ RCT12.h:570, RCT12.h:259: libopenrct2 RCT12.h 570

struct RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};
struct rct1_peep : RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};

Իհարկե, նույն անունով փոփոխական օգտագործելը բազային դասում և հետնորդում միշտ չէ, որ սխալ է: Այնուամենայնիվ, ժառանգական տեխնոլոգիան ինքնին ենթադրում է, որ ծնող դասի բոլոր դաշտերը առկա են երեխայի դասարանում: Ժառանգում հայտարարելով նույն անունով դաշտերը՝ մենք շփոթություն ենք ստեղծում։

Զգուշացում N4

V793 Տարօրինակ է, որ «imageDirection / 8» հայտարարության արդյունքը պայմանի մի մասն է: Թերևս պետք էր այս հայտարարությունը համեմատել այլ բանի հետ։ libopenrct2 ObservationTower.cpp 38

void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
  if ((imageDirection / 8) && (imageDirection / 8) != 3)
  {
    ....
  }
  ....
}

Եկեք ավելի սերտ նայենք: Արտահայտություն imageDirection/8 կեղծ կլինի, եթե պատկերի ուղղություն գտնվում է -7-ից 7-ի սահմաններում: Երկրորդ մաս. (imageDirection / 8) != 3 ստուգումներ պատկերի ուղղություն միջակայքից դուրս գտնվելու համար՝ համապատասխանաբար -31-ից -24 և 24-ից 31: Ինձ համար բավականին տարօրինակ է թվում այս կերպ ստուգել թվերը որոշակի տիրույթում ներառելու համար, և, նույնիսկ եթե այս կոդի մեջ սխալ չկա, ես խորհուրդ կտայի վերաշարադրել այս պայմանները, որպեսզի ավելի հստակ լինի: Սա շատ ավելի կհեշտացնի կյանքը այն մարդկանց համար, ովքեր կկարդան և կպահպանեն այս կոդը:

Զգուշացում N5

V587 Այս տեսակի առաջադրանքների տարօրինակ հաջորդականություն. A = B; B = A;. Ստուգեք տողերը՝ 1115, 1118. libopenrct2ui MouseInput.cpp 1118

void process_mouse_over(....)
{
  ....
  switch (window->widgets[widgetId].type)
  {
    case WWT_VIEWPORT:
      ebx = 0;
      edi = cursorId;                                 // <=
      // Window event WE_UNKNOWN_0E was called here,
      // but no windows actually implemented a handler and
      // it's not known what it was for
      cursorId = edi;                                 // <=
      if ((ebx & 0xFF) != 0)
      {
        set_cursor(cursorId);
        return;
      }
      break;
      ....
  }
  ....
}

Կոդի այս հատվածը, ամենայն հավանականությամբ, ստացվել է ապակոմպիլյացիայի միջոցով: Հետո, դատելով թողած մեկնաբանությունից, չաշխատող կոդի մի մասը հանվել է։ Այնուամենայնիվ, դեռ մի քանի վիրահատություն է մնացել կուրսորի ID, որոնք նույնպես այնքան էլ իմաստ չունեն:

Զգուշացում N6

V1004 [CWE-476] «Նվագարկիչ» ցուցիչը օգտագործվել է անապահով այն բանից հետո, երբ այն հաստատվել է nullptr-ի հետ: Ստուգեք տողերը՝ 2085, 2094. libopenrct2 Network.cpp 2094

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Add new player.
    player = AddPlayer("", "");
    if (player)                                          // <=
    {
      *player = pendingPlayer;
       if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
       {
         _serverConnection->Player = player;
       }
    }
    newPlayers.push_back(player->Id);                    // <=
  }
  ....
}

Այս կոդը բավականին հեշտ է ուղղել, պարզապես անհրաժեշտ է այն երրորդ անգամ ստուգել խաղացող զրոյական ցուցիչի վրա կամ ավելացրե՛ք այն պայմանական հայտարարության մարմնին: Ես կառաջարկեի երկրորդ տարբերակը.

void Network::ProcessPlayerList()
{
  ....
  auto* player = GetPlayerByID(pendingPlayer.Id);
  if (player == nullptr)
  {
    // Add new player.
    player = AddPlayer("", "");
    if (player)
    {
      *player = pendingPlayer;
      if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
      {
        _serverConnection->Player = player;
      }
      newPlayers.push_back(player->Id);
    }
  }
  ....
}

Զգուշացում N7

V547 [CWE-570] «name == nullptr» արտահայտությունը միշտ կեղծ է: libopenrct2 ServerList.cpp 102

std::optional<ServerListEntry> ServerListEntry::FromJson(...)
{
  auto name = json_object_get(server, "name");
  .....
  if (name == nullptr || version == nullptr)
  {
    ....
  }
  else
  {
    ....
    entry.name = (name == nullptr ? "" : json_string_value(name));
    ....
  }
  ....
}

Դուք կարող եք մեկ հարվածով ազատվել կոդի դժվար ընթեռնելի տողից և լուծել խնդիրը՝ ստուգելով nullptr. Առաջարկում եմ ծածկագիրը փոխել հետևյալ կերպ.

std::optional<ServerListEntry> ServerListEntry::FromJson(...)
{
  auto name = json_object_get(server, "name");
  .....
  if (name == nullptr || version == nullptr)
  {
    name = ""
    ....
  }
  else
  {
    ....
    entry.name = json_string_value(name);
    ....
  }
  ....
}

Զգուշացում N8

V1048 [CWE-1164] «ColumnHeaderPressedCurrentState» փոփոխականին վերագրվել է նույն արժեքը: libopenrct2ui CustomListView.cpp 510

void CustomListView::MouseUp(....)
{
  ....
  if (!ColumnHeaderPressedCurrentState)
  {
    ColumnHeaderPressed = std::nullopt;
    ColumnHeaderPressedCurrentState = false;
    Invalidate();
  }
}

Կոդը բավականին տարօրինակ տեսք ունի։ Ինձ թվում է՝ տառասխալ է եղել կա՛մ պայմանում, կա՛մ փոփոխականը վերահանձնելիս ColumnHeaderPressedCurrentState արժեքներ սուտ.

Արտադրողականություն

Ինչպես տեսնում ենք, PVS-Studio ստատիկ անալիզատորի ինտեգրումը ձեր TeamCity նախագծին բավականին պարզ է: Դա անելու համար բավական է գրել ընդամենը մեկ փոքր կոնֆիգուրացիայի ֆայլ: Կոդի ստուգումը թույլ կտա հայտնաբերել խնդիրները հավաքումից անմիջապես հետո, ինչը կօգնի վերացնել դրանք, երբ փոփոխությունների բարդությունն ու արժեքը դեռևս ցածր են:

PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն
Եթե ​​ցանկանում եք կիսվել այս հոդվածով անգլիախոս լսարանի հետ, խնդրում ենք օգտագործել թարգմանության հղումը՝ Վլադիսլավ Ստոլյարով: PVS-Studio և Continuous Integration. TeamCity: Open RollerCoaster Tycoon 2 նախագծի վերլուծություն.

Source: www.habr.com

Добавить комментарий