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

该代码看起来很奇怪。 在我看来,条件或重新分配变量时存在拼写错误 列标题按下当前状态false.

结论

正如我们所看到的,将 PVS-Studio 静态分析器集成到您的 TeamCity 项目中非常简单。 为此,只需编写一个小配置文件就足够了。 检查代码将使您能够在组装后立即识别问题,这将有助于在更改的复杂性和成本仍然较低时消除问题。

PVS-Studio 和持续集成:TeamCity。 开放过山车大亨2项目分析
如果您想与英语读者分享这篇文章,请使用翻译链接:Vladislav Stolyarov。 PVS-Studio 和持续集成:TeamCity。 开放过山车大亨2项目分析.

来源: habr.com

添加评论