PVS-Studio和持续集成:TeamCity。Open RollerCoaster Tycoon 2项目分析



使用PVS-Studio分析仪最相关的场景之一是其与CI系统的集成。而且,尽管几乎所有连续集成系统对PVS-Studio项目的分析都可以内置到几个命令中,但我们仍将继续简化此过程。PVS-Studio现在支持将分析仪输出转换为TeamCity-TeamCity检查类型的格式。让我们看看它是如何工作的。



有关所用软件的信息



PVS-Studio是,++,C#和Java代码的静态分析器,旨在促进发现和修复各种错误的任务。该分析仪可以在Windows,Linux和macOS上使用。在本文中,我们不仅将积极使用分析仪本身,还将使用其分发工具包中的一些实用程序。



CLMonitor是用于监视编译器启动的监视服务器。必须在开始构建项目之前立即运行它。在监视模式下,服务器将拦截所有受支持的编译器的运行。应该注意的是,该实用程序只能用于分析C / C ++项目。



PlogConverter是用于将分析仪报告转换为不同格式的实用程序。



有关调查项目的信息



让我们通过一个实际的示例来尝试此功能-让我们分析OpenRCT2项目。



OpenRCT2是RollerCoaster Tycoon 2(RCT2)的开源实现,并通过新功能和错误修复对其进行了扩展。游戏围绕着一个游乐园的建设和维护,该游乐园设有景点,商店和设施。玩家应设法使公园获利并保持良好的声誉,同时让客人满意。OpenRCT2允许您同时播放场景和沙箱。场景要求玩家在设定的时间完成特定任务,而沙箱则允许玩家在没有任何限制或财务的情况下建立更灵活的公园。



配置



为了节省时间,我可能会跳过安装过程,并从TeamCity服务器在计算机上运行的那一刻开始。我们需要转到:localhost:{安装期间指定的端口}(在我的情况下,localhost:9090)并输入授权数据。进入后,我们将遇到:



image3.png


单击创建项目按钮。接下来,选择“手动”,填写字段。



image5.png


单击“创建”按钮后,将出现一个带有设置的窗口。



image7.png


单击创建构建配置



image9.png


填写字段,点击创建我们看到一个提供选择版本控制系统的窗口。由于源已经位于本地,因此单击“跳过”



image11.png


最后,我们进入项目设置。



image13.png


通过单击添加构建步骤:构建步骤->添加构建步骤



image15.png


在这里我们选择:



  • 流道类型->命令行
  • 运行->自定义脚本


由于我们将在项目编译过程中进行分析,因此组装和分析应该是第一步,因此请填写“自定义脚本”字段



image17.png


稍后我们将详细说明各个步骤。重要的是,加载分析器,构建项目,对其进行分析,输出报告并对其进行格式化仅需要十一行代码。



我们需要做的最后一件事是设置环境变量,我已经以某种方式指出了这些变量以提高其可读性。为此,请转到:参数->添加新参数并添加三个变量:



image19.png


仍然需要单击右上角的“运行”按钮在组装和分析项目时,我将向您介绍脚本。



直接脚本



首先,我们需要下载新的PVS-Studio分发套件。为此,我们使用hocolatey软件包管理器。对于那些想更多地了解这一点的人,有一篇相关的文章



choco install pvs-studio -y


接下来,让我们启动CLMonitor项目组装跟踪实用程序。



%CLmon% monitor –-attach


然后,我们将构建项目,将我需要构建的MSBuild版本路径用作MSB环境变量。



%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:\temp\ptest.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:\temp\ptest.plog_TeamCity.txt"


在此期间,项目的组装和分析已成功完成,我们可以转到“项目”选项卡并进行验证。



image21.png


现在,单击“检查总数”以查看分析器报告:



image23.png


警告按诊断规则编号分组。要浏览代码,您需要单击带有警告的行号。单击右上角的问号将为您打开一个新的文档选项卡。您还可以通过单击解析器警告行号来浏览代码。使用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秘籍.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观察塔.cpp 38



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


让我们仔细看看。如果imageDirection的范围是-7到7,imageDirection / 8表达式将为false 。第二部分:(imageDirection / 8)!= 3会检查imageDirection超出范围:-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;
      ....
  }
  ....
}


这段代码很可能是通过反编译获得的。然后,根据剩下的注释判断,删除了一些无效代码。但是,对cursorId的一些操作仍然没有意义。



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


您可以一口气摆脱难以阅读的代码行,并通过检查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重新分配为false时



输出量



如我们所见,将PVS-Studio静态分析器集成到TeamCity项目中非常简单。为此,您只需要编写一个小型配置文件。代码审查将使您能够在构建后立即发现问题,这将有助于在复杂性和编辑成本仍然很小的情况下消除问题。





如果您想与说英语的读者分享这篇文章,请使用翻译链接:Vladislav Stolyarov。PVS-Studio和持续集成:TeamCity。Open RollerCoaster Tycoon 2项目的分析



All Articles