PVS-Studio และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2

PVS-Studio และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2
หนึ่งในสถานการณ์ปัจจุบันที่สุดสำหรับการใช้เครื่องวิเคราะห์ PVS-Studio คือการผสานรวมกับระบบ CI และถึงแม้ว่าการวิเคราะห์โปรเจ็กต์ PVS-Studio จากระบบบูรณาการต่อเนื่องเกือบทุกระบบจะสามารถสร้างได้โดยใช้คำสั่งเพียงไม่กี่คำสั่ง แต่เรายังคงทำให้กระบวนการนี้สะดวกยิ่งขึ้นต่อไป ขณะนี้ PVS-Studio รองรับการแปลงเอาต์พุตของตัววิเคราะห์เป็นรูปแบบสำหรับ TeamCity - TeamCity Inspections Type มาดูกันว่ามันทำงานอย่างไร

ข้อมูลเกี่ยวกับซอฟต์แวร์ที่ใช้

พีวีเอส-สตูดิโอ — ตัววิเคราะห์แบบคงที่ของโค้ด C, C++, C# และ Java ออกแบบมาเพื่ออำนวยความสะดวกในการค้นหาและแก้ไขข้อผิดพลาดประเภทต่างๆ เครื่องวิเคราะห์สามารถใช้ได้บน Windows, Linux และ macOS ในบทความนี้เราจะใช้งานไม่เพียงแต่ตัววิเคราะห์เท่านั้น แต่ยังรวมถึงยูทิลิตี้บางอย่างจากการจำหน่ายด้วย

CLMonitor — เป็นเซิร์ฟเวอร์การมอนิเตอร์ที่มอนิเตอร์การเปิดตัวคอมไพเลอร์ ต้องรันทันทีก่อนที่จะเริ่มสร้างโปรเจ็กต์ของคุณ ในโหมดสอดแนม เซิร์ฟเวอร์จะสกัดกั้นการทำงานของคอมไพเลอร์ที่รองรับทั้งหมด เป็นที่น่าสังเกตว่ายูทิลิตี้นี้สามารถใช้เพื่อวิเคราะห์โปรเจ็กต์ C/C++ เท่านั้น

PlogConverter – ยูทิลิตี้สำหรับการแปลงรายงานตัววิเคราะห์เป็นรูปแบบต่างๆ

ข้อมูลเกี่ยวกับโครงการที่อยู่ระหว่างการศึกษา

ลองใช้ฟังก์ชันนี้กับตัวอย่างเชิงปฏิบัติ - มาวิเคราะห์โครงการ OpenRCT2 กัน

เปิด - การใช้งานแบบเปิดของเกม RollerCoaster Tycoon 2 (RCT2) ขยายด้วยฟังก์ชั่นใหม่และแก้ไขข้อบกพร่อง รูปแบบเกมเกี่ยวข้องกับการสร้างและดูแลรักษาสวนสนุกที่มีเครื่องเล่น ร้านค้า และสิ่งอำนวยความสะดวกต่างๆ ผู้เล่นจะต้องพยายามทำกำไรและรักษาชื่อเสียงที่ดีของสวนสนุกในขณะเดียวกันก็ทำให้แขกมีความสุข OpenRCT2 ให้คุณเล่นได้ทั้งในสถานการณ์และแซนด์บ็อกซ์ สถานการณ์ต้องการให้ผู้เล่นทำงานเฉพาะให้เสร็จสิ้นภายในเวลาที่กำหนด ในขณะที่ Sandbox อนุญาตให้ผู้เล่นสร้างสวนสาธารณะที่ยืดหยุ่นมากขึ้นโดยไม่มีข้อจำกัดหรือการเงิน

การตั้งค่า

เพื่อประหยัดเวลา ฉันอาจจะข้ามขั้นตอนการติดตั้งและเริ่มจากช่วงเวลาที่ฉันมีเซิร์ฟเวอร์ TeamCity ทำงานบนคอมพิวเตอร์ของฉัน เราต้องไปที่: localhost:{port specified ในระหว่างกระบวนการติดตั้ง} (ในกรณีของฉัน localhost:9090) และป้อนข้อมูลการอนุญาต หลังจากเข้ามาแล้วเราจะพบกับ:

PVS-Studio และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2
คลิกที่ปุ่มสร้างโครงการ จากนั้นเลือกด้วยตนเอง และกรอกข้อมูลในฟิลด์

PVS-Studio และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2
หลังจากกดปุ่ม สร้างบัญชีตัวแทนเราได้รับการต้อนรับจากหน้าต่างพร้อมการตั้งค่า

PVS-Studio และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2
คลิกกันได้เลย สร้างการกำหนดค่าบิลด์.

PVS-Studio และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2
กรอกข้อมูลลงในช่องแล้วคลิก สร้างบัญชีตัวแทน. เราเห็นหน้าต่างขอให้คุณเลือกระบบควบคุมเวอร์ชัน เนื่องจากแหล่งที่มามีอยู่ในเครื่องแล้ว คลิก ข้ามไป.

PVS-Studio และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2
ในที่สุด เราก็ไปยังการตั้งค่าโปรเจ็กต์

PVS-Studio และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2
มาเพิ่มขั้นตอนการประกอบกัน โดยคลิก: ขั้นตอนการสร้าง -> เพิ่มขั้นตอนการสร้าง.

PVS-Studio และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2
ที่นี่เราเลือก:

  • ประเภทนักวิ่ง -> บรรทัดคำสั่ง
  • เรียกใช้ -> สคริปต์ที่กำหนดเอง

เนื่องจากเราจะทำการวิเคราะห์ในระหว่างการรวบรวมโครงการ การประกอบและการวิเคราะห์ควรเป็นขั้นตอนเดียว ดังนั้นให้กรอกข้อมูลในฟิลด์ สคริปต์ที่กำหนดเอง:

PVS-Studio และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2
เราจะดูแต่ละขั้นตอนในภายหลัง สิ่งสำคัญคือการโหลดตัววิเคราะห์ การประกอบโปรเจ็กต์ การวิเคราะห์ การส่งออกรายงาน และการจัดรูปแบบนั้นใช้โค้ดเพียงสิบเอ็ดบรรทัดเท่านั้น

สิ่งสุดท้ายที่เราต้องทำคือตั้งค่าตัวแปรสภาพแวดล้อม ซึ่งฉันได้สรุปวิธีการบางอย่างในการปรับปรุงให้อ่านง่ายขึ้น หากต้องการทำสิ่งนี้ เรามาเริ่มกันเลย: พารามิเตอร์ -> เพิ่มพารามิเตอร์ใหม่ และเพิ่มตัวแปรสามตัว:

PVS-Studio และการรวมอย่างต่อเนื่อง: 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"

ขั้นตอนสุดท้ายคือการแสดงรายงานที่จัดรูปแบบแล้ว แย่โดยที่ตัวแยกวิเคราะห์ 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 และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2
ตอนนี้เรามาคลิกที่ การตรวจสอบรวมเพื่อเข้าไปดูรายงานตัววิเคราะห์:

PVS-Studio และการรวมอย่างต่อเนื่อง: 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;
}

เครื่องวิเคราะห์สังเกตเห็นข้อผิดพลาดหลังจากจัดสรรหน่วยความจำแบบไดนามิกแล้ว สร้างวัตถุเมื่อมีข้อยกเว้นเกิดขึ้น หน่วยความจำจะไม่ถูกล้าง และหน่วยความจำรั่วเกิดขึ้น

คำเตือน N2

V501 มีนิพจน์ย่อยที่เหมือนกัน '(1ULL << WIDX_MONTH_BOX)' ทางด้านซ้ายและทางด้านขวาของ '|' ตัวดำเนินการ libopenrct2ui สูตรโกง 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),
  ....
};

มีเพียงไม่กี่คนที่ไม่ใช่เครื่องวิเคราะห์แบบคงที่ที่สามารถผ่านการทดสอบความใส่ใจนี้ได้ ตัวอย่างการคัดลอกและวางนี้ดีด้วยเหตุผลนี้

คำเตือน 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)
  {
    ....
  }
  ....
}

มาดูกันดีกว่า การแสดงออก ภาพทิศทาง/8 จะเป็นเท็จถ้า ภาพทิศทาง อยู่ในช่วงตั้งแต่ -7 ถึง 7 ส่วนที่สอง: (imageDirection / 8) != 3 เช็ค ภาพทิศทาง สำหรับการอยู่นอกช่วง: จาก -31 ถึง -24 และจาก 24 ถึง 31 ตามลำดับ ดูเหมือนว่าค่อนข้างแปลกสำหรับฉันที่จะตรวจสอบตัวเลขเพื่อรวมในช่วงใดช่วงหนึ่งในลักษณะนี้ และแม้ว่าจะไม่มีข้อผิดพลาดในโค้ดส่วนนี้ ฉันขอแนะนำให้เขียนเงื่อนไขเหล่านี้ใหม่เพื่อให้ชัดเจนยิ่งขึ้น สิ่งนี้จะทำให้ชีวิตง่ายขึ้นมากสำหรับผู้ที่อ่านและดูแลรักษาโค้ดนี้

คำเตือน N5

V587 ลำดับคี่ของการมอบหมายประเภทนี้: A = B; ข = ก;. ตรวจสอบบรรทัด: 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;
      ....
  }
  ....
}

ส่วนของโค้ดนี้น่าจะได้มาจากการคอมไพล์ จากนั้นเมื่อพิจารณาจากความคิดเห็นที่เหลือ โค้ดที่ไม่ทำงานบางส่วนก็ถูกลบออก อย่างไรก็ตาม ยังมีการดำเนินการอีกสองสามรายการที่เหลืออยู่ รหัสเคอร์เซอร์ซึ่งก็ไม่สมเหตุสมผลเช่นกัน

คำเตือน 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();
  }
}

รหัสดูค่อนข้างแปลก สำหรับฉันดูเหมือนว่ามีการพิมพ์ผิดไม่ว่าจะในเงื่อนไขหรือเมื่อกำหนดตัวแปรใหม่ คอลัมน์ HeaderPressedCurrentState ความหมาย เท็จ.

เอาท์พุต

ดังที่เราเห็นแล้วว่าการรวมเครื่องวิเคราะห์แบบคงที่ PVS-Studio เข้ากับโปรเจ็กต์ TeamCity ของคุณนั้นค่อนข้างง่าย ในการดำเนินการนี้ การเขียนไฟล์การกำหนดค่าขนาดเล็กเพียงไฟล์เดียวก็เพียงพอแล้ว การตรวจสอบโค้ดจะช่วยให้คุณสามารถระบุปัญหาได้ทันทีหลังการประกอบ ซึ่งจะช่วยขจัดปัญหาเหล่านี้เมื่อความซับซ้อนและต้นทุนของการเปลี่ยนแปลงยังต่ำ

PVS-Studio และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2
หากคุณต้องการแบ่งปันบทความนี้กับผู้ชมที่พูดภาษาอังกฤษ โปรดใช้ลิงก์การแปล: Vladislav Stolyarov PVS-Studio และการรวมอย่างต่อเนื่อง: TeamCity วิเคราะห์โครงการ Open RollerCoaster Tycoon 2.

ที่มา: will.com

เพิ่มความคิดเห็น