PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析

PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析
目前使用 PVS-Studio 分析儀的場景之一是其與 CI 系統的整合。 儘管幾乎任何持續整合系統的 PVS-Studio 專案分析都可以建置到幾個命令中,但我們繼續讓這個過程變得更加方便。 PVS-Studio 現在支援將分析器輸出轉換為 TeamCity - TeamCity 檢查類型的格式。 讓我們看看它是如何工作的。

有關所使用軟體的信息

PVS工作室 — C、C++、C# 和 Java 程式碼的靜態分析器,旨在促進尋找和修正各種類​​型錯誤的任務。 此分析儀可在 Windows、Linux 和 macOS 上使用。 在本文中,我們不僅將積極使用分析器本身,還將積極使用其發行版中的一些實用程式。

CL監控器 — 是監視編譯器啟動的監視伺服器。 它必須在開始建置專案之前立即運行。 在監聽模式下,伺服器將攔截所有支援的編譯器的運作。 值得注意的是,該實用程式只能用於分析 C/C++ 專案。

Plog轉換器 – 用於將分析器報告轉換為不同格式的實用程式。

有關正在研究的項目的信息

讓我們透過一個實際範例來嘗試此功能 - 讓我們分析 OpenRCT2 專案。

開放RCT2 - 遊戲 RollerCoaster Tycoon 2 (RCT2) 的開放實現,透過新功能擴展並修復錯誤。 遊戲玩法圍繞著建造和維護一個包含遊樂設施、商店和設施的遊樂園。 玩家必須努力賺取利潤並維護公園的良好聲譽,同時讓遊客滿意。 OpenRCT2 可讓您在場景和沙盒中進行遊戲。 場景要求玩家在規定時間內完成特定任務,而沙盒則允許玩家在沒有任何限製或財務的情況下建造更靈活的公園。

調整

為了節省時間,我可能會跳過安裝過程,從 TeamCity 伺服器在我的電腦上運行的那一刻開始。 我們需要存取: localhost:{安裝過程中指定的連接埠}(在我的例子中為 localhost:9090)並輸入授權資料。 進入後,迎接我們的是:

PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析
點選“建立專案”按鈕。 接下來,選擇手動並填寫欄位。

PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析
按下按鈕後 創建,我們會看到一個有設定的視窗。

PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析
我們點擊一下 建立建置配置.

PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析
填寫欄位並點擊 創建。 我們看到一個窗口,要求您選擇版本控制系統。 由於來源已位於本機,因此按一下 跳至.

PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析
最後,我們繼續進行專案設定。

PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析
讓我們新增組裝步驟,為此點擊: 建置步驟 -> 新增建置步驟.

PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析
這裡我們選擇:

  • 運行器類型 -> 命令列
  • 運行 -> 自訂腳本

由於我們會在專案編譯時進行分析,所以組裝和分析應該是一步,所以填寫該字段 自定義腳本:

PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析
我們稍後將討論各個步驟。 重要的是,載入分析器、組裝專案、分析它、輸出報告並格式化它只需要十一行程式碼。

我們需要做的最後一件事是設定環境變量,我已經概述了一些提高其可讀性的方法。 為此,我們繼續: 參數 -> 新增參數 並加入三個變數:

PVS-Studio 和持續整合:TeamCity。 開放過山車大亨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。 開放過山車大亨2專案分析
現在我們點擊 檢查總數轉到查看分析器報告:

PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析
警告按診斷規則編號分組。 要瀏覽程式碼,您需要點擊帶有警告的行號。 點擊右上角的問號將開啟一個包含文件的新分頁。 您也可以透過點擊帶有分析器警告的行號來瀏覽程式碼。 使用時可以從遠端電腦進行導航 源樹根 標記。 對分析儀的這種操作模式感興趣的人可以熟悉相應的部分 文件.

查看分析儀的結果

現在我們已經完成了建置的部署和配置,讓我們來看看我們正在查看的專案中發現的一些有趣的警告。

警告 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」中的「flags」欄位覆蓋了基底類別「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。第二部分: (影像方向 / 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;
      ....
  }
  ....
}

該程式碼片段很可能是透過反編譯獲得的。 然後,從留下的評論來看,部分不起作用的程式碼被刪除了。 然而,還剩下一些手術 遊標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」總是 false。 libopenrct2 伺服器列表.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));
    ....
  }
  ....
}

您可以一舉擺脫一行難以閱讀的程式碼,並透過檢查來解決問題 空指針。 我建議更改程式碼如下:

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

該代碼看起來很奇怪。 在我看來,條件或重新分配變數時存在拼字錯誤 列標題按下目前狀態.

產量

正如我們所看到的,將 PVS-Studio 靜態分析器整合到您的 TeamCity 專案中非常簡單。 為此,只需編寫一個小設定檔就足夠了。 檢查程式碼將使您能夠在組裝後立即識別問題,這將有助於在更改的複雜性和成本仍然較低時消除問題。

PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析
如果您想與英語讀者分享這篇文章,請使用翻譯連結:Vladislav Stolyarov。 PVS-Studio 和持續整合:TeamCity。 開放過山車大亨2專案分析.

來源: www.habr.com

添加評論