PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
One of the most relevant use cases for the PVS-Studio analyzer is its integration with CI systems. Although the analysis of a project with PVS-Studio can be integrated into almost any continuous integration system with just a few commands, we continue to make this process even more convenient. PVS-Studio now supports converting the analyzer's output into a format for TeamCity — TeamCity Inspections Type. Let's see how this works.

Information about the software used

PVS-Studio — a static analyzer for C, C++, C#, and Java code, designed to facilitate the task of finding and fixing various types of bugs. The analyzer can be used on Windows, Linux, and macOS. In this article, we will actively use not only the analyzer itself but also some utilities from its distribution.

CLMonitor — is a monitoring server that tracks compiler launches. It must be started just before the beginning of the project build. In tracing mode, the server will intercept the launches of all supported compilers. It is worth noting that this utility can only be used for analyzing C/C++ projects.

PlogConverter – a utility for converting the analyzer report into various formats.

Information about the project being examined

Let's try this functionality with a practical example – we will analyze the OpenRCT2 project.

OpenRCT2 — an open implementation of the game RollerCoaster Tycoon 2 (RCT2), enhancing it with new features and fixing bugs. The gameplay revolves around the construction and management of an amusement park that includes rides, shops, and facilities. The player must strive to make a profit and maintain a good reputation for the park while keeping guests happy. OpenRCT2 allows playing both scenarios and sandbox mode. Scenarios require the player to complete specific tasks within a set time, while sandbox mode allows the player to build a more flexible park without any restrictions or financial concerns.

Settings

To save time, I will skip the installation process and begin from the moment when the TeamCity server is running on my computer. We need to navigate to: localhost:{the port specified during installation}(in my case, localhost:9090) and enter the login credentials. After logging in, we will be greeted by:

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
We'll click the Create Project button. Next, we'll choose Manually and fill in the fields.

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
After clicking the button Create, a window with settings greets us.

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
Let's click Create build configuration.

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
We fill in the fields and click Create. We see a window offering to choose a version control system. Since the source files are already stored locally, we click Skip.

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
Finally, we move on to the project settings.

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
We'll add build steps, for this we click: Build steps -> Add build step.

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
Here we will select:

  • Runner type -> Command Line
  • Run -> Custom Script

Since we will conduct analysis during the project compilation, building and analyzing must be one step, so we will fill in the field Custom Script:

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
We will stop at separate steps later. It is important that loading the analyzer, building the project, analyzing it, generating the report, and formatting it take a total of eleven lines of code.

The last thing we need to do is set environment variables, which I have designated some paths for readability. For this, we go to: Parameters -> Add new parameter and add three variables:

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
We just need to click the button Run in the upper right corner. While the project is being built and analyzed, I will tell you about the script.

Specifically the script

First, we need to download the latest PVS-Studio distribution. For this, we will use the Chocolatey package manager. For those who want to know more about this, there is a corresponding article:

choco install pvs-studio -y

Next, we will run the project build tracking utility CLMonitor.

%CLmon% monitor --attach

Then we will build the project, where the environment variable MSB represents the path to the required version of MSBuild for the build

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

We will enter the username and license key for PVS-Studio:

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

After the build is completed, we will run CLMonitor again for generating preprocessed files and performing static analysis:

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

Then we will use another utility from our distribution. PlogConverter converts the report from standard format to TeamCity specific format. This way we can view it right in the build window.

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

As the final action, we will output the formatted report to stdout, where it will be picked up by the TeamCity parser.

type "C:tempptest.plog_TeamCity.txt"

Full script code:

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"

Meanwhile, the project build and analysis have successfully completed, we can move to the tab Projects and verify this.

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
Now let's click on Inspections Total, to view the analyzer report:

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
Warnings are grouped by the diagnostic rule numbers. To navigate through the code, click on the line number with a warning. Clicking on the question mark in the top right corner will open a new tab with documentation. You can also navigate through the code by clicking on the line number with a warning from the analyzer. Remote navigation is possible using SourceTreeRoot marker. Those interested in this analyzer work mode can refer to the appropriate section the documentation.

Viewing Analyzer Results

After we finished deploying and configuring the build, I suggest looking at some interesting warnings found in the examined project.

Warning N1

V773 [CWE-401] The exception was thrown without releasing the 'result' pointer. A memory leak is possible. 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;
}

The analyzer has detected an error indicating that after dynamic memory allocation in CreateObject, upon throwing an exception, the memory is not freed, leading to a memory leak.

Warning N2

V501 There are identical sub-expressions '(1ULL << WIDX_MONTH_BOX)' to the left and to the right of the '|' operator. 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),
  ....
};

Few, besides static analyzers, could pass this test of attentiveness. This example of copy-pasting is good for exactly that reason.

Warning N3

V703 It is odd that the 'flags' field in derived class 'RCT12BannerElement' overwrites the field in base class 'RCT12TileElementBase'. Check lines: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

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

Naturally, using a variable with the same name in the base class and in the derived class is not always a mistake. However, inheritance technology itself implies the presence of all fields of the parent class in the child class. By declaring fields with the same name in the derived class, we introduce confusion.

Warning N4

V793 It is odd that the result of the 'imageDirection / 8' statement is part of the condition. Perhaps, this statement should have been compared with something else. libopenrct2 ObservationTower.cpp 38

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

Let's figure this out in more detail. The expression imageDirection / 8 will be false if imageDirection is in the range from -7 to 7. The second part: (imageDirection / 8) != 3 checks for being outside the range: from -31 to -24 and from 24 to 31. I find it quite strange to check numbers for inclusion in a certain range this way, and even if there is no error in this code snippet, I would recommend rewriting these conditions to be more explicit. This would significantly simplify life for those who will read and maintain this code. imageDirection V587

Warning N5

An odd sequence of assignments of this kind: A = B; B = A;. Check lines: 1115, 1118. libopenrct2ui MouseInput.cpp 1118 An odd sequence of assignments of this kind: A = B; B = A;. Check lines: 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;
      ....
  }
  ....
}

This code fragment was likely obtained through decompilation. Then, judging by the remaining comment, part of the non-working code was removed. However, a couple of operations over cursorId, which also don't carry much meaning.

Warning N6

V1004 [CWE-476] The 'player' pointer was used unsafely after it was verified against nullptr. Check lines: 2085, 2094. libopenrct2 Network.cpp 2094

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

This code can be easily fixed; we either need to check the pointer against null again or place it within the conditional operator. I would suggest the latter option: player against a null pointer, or include it within the body of the conditional operator. I would recommend the second option:

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

Warning N7

V547 [CWE-570] Expression 'name == nullptr' is always false. libopenrct2 ServerList.cpp 102

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

We can eliminate the hard-to-read line of code in one go and solve the issue with the null check. nullptrI propose modifying the code as follows:

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

Warning N8

V1048 [CWE-1164] The 'ColumnHeaderPressedCurrentState' variable was assigned the same value. libopenrct2ui CustomListView.cpp 510

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

The code looks quite strange. It seems to me there was a typo either in the condition or during the reassignment of the variable. ColumnHeaderPressedCurrentState values false.

Output

As we can see, integrating the PVS-Studio static analyzer into your project on TeamCity is quite straightforward. All it takes is to write a small configuration file. Code analysis will help identify problems right after the build, allowing for their resolution when the complexity and cost of fixes are still low.

PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project
If you want to share this article with an English-speaking audience, please use the link to the translation: Vladislav Stolyarov. PVS-Studio and Continuous Integration: TeamCity. Analysis of the Open RollerCoaster Tycoon 2 project.

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster