This is a new piece of our series of articles about using the PVS-Studio static analyzer with cloud CI systems. Today we are going to look at another service, CircleCI. We'll take the Kodi media player application as a test project and see if we can find any interesting bugs in its source code.
To get current information about it follow the updated documentation page "Using with CircleCI".
Note. The previous articles on integrating PVS-Studio with cloud CI systems:
Before setting up the work environment and examining the analysis report, I'd like to say a few words about the software we are going to use and check.
CircleCI is a cloud CI service for automated software building, testing, and deployment. It supports project building both in containers and on virtual machines on Windows, Linux, and macOS.
Kodi is a free and open-source cross-platform media player application. It allows users to play and view most streaming media, such as videos, music, podcasts, and videos from the Internet, as well as all common digital media files from local and network storage media. It supports the use of themes and skins and functionality extensions through plugins. Kodi is available for Windows, Linux, macOS, and Android.
PVS-Studio is a static analyzer for detecting bugs and potential vulnerabilities in the source code of applications written in C, C++, C#, and Java. The analyzer runs on Windows, Linux, and macOS.
First we need to go to the CircleCI main page and click "Sign Up"
On the next page, we are offered to authorize with a GitHub or Bitbucket account. We choose GitHub and get to the CircleCI authorization page.
After authorizing the application (by clicking the green button "Authorize circleci"), we are redirected to the "Welcome to CircleCI!" page:
Here we can specify right away which projects we want CircleCI to build. We tick our repository and click "Follow".
After adding the repository, CircleCI will automatically start the build process, but since we don't have a configuration file in our repository yet, the build job will be aborted with an error message.
Before adding a configuration file, we need to add a couple of variables containing analyzer license data. To do that, we click "Settings" on the left sidebar, select "Projects" in the "ORGANIZATION" section, and click the cogwheel button to the right of our project's name. A settings window will appear.
We go to the "Environment Variables" page. Here we create two variables, PVS_USERNAME and PVS_KEY, which contain the username and analyzer license key.
When starting the build, CircleCI reads the job configuration from the file stored in the repository at .circleci/config.yml. Let's add it.
First we need to specify the image of the virtual machine that the analyzer will be running on. The complete list of images is available here.
version: 2
jobs:
build:
machine:
image: ubuntu-1604:201903-01
Next, we add the necessary repositories to apt and install the project's dependencies:
steps:
- checkout
- run: sudo -- sh -c "
add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends
&& add-apt-repository -y ppa:wsnipex/vaapi
&& add-apt-repository -y ppa:pulse-eight/libcec
&& apt-get update"
- run: sudo apt-get install -y
automake autopoint build-essential cmake
curl default-jre gawk gdb gdc gettext git-core
gperf libasound2-dev libass-dev libbluray-dev
libbz2-dev libcap-dev libcdio-dev libcec4-dev
libcrossguid-dev libcurl3 libcurl4-openssl-dev
libdbus-1-dev libegl1-mesa-dev libfmt3-dev
libfontconfig-dev libfreetype6-dev libfribidi-dev
libfstrcmp-dev libgif-dev libgl1-mesa-dev
libglu1-mesa-dev libiso9660-dev libjpeg-dev
liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev
libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev
libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev
libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev
libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev
libxrandr-dev libxrender-dev libxslt1-dev libxt-dev
mesa-utils nasm pmount python-dev python-imaging
python-sqlite rapidjson-dev swig unzip uuid-dev yasm
zip zlib1g-dev wget
Adding the PVS-Studio repository and installing the analyzer:
- run: wget -q -O - https://files.pvs-studio.com/etc/pubkey.txt
| sudo apt-key add -
&& sudo wget -O /etc/apt/sources.list.d/viva64.list
https://files.pvs-studio.com/etc/viva64.list
- run: sudo -- sh -c "apt-get update
&& apt-get install pvs-studio -y"
Then we are building the dependencies:
- run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local
After that, we generate Makefiles in the build directory:
- run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug ..
The next step is setting up and starting an analysis of the project.
First we create an analyzer license file. Another command will start tracing the project build by the compiler.
The next command following the tracing runs the analysis as such. If you use a demo version of PVS-Studio, launch it with the parameter:
‑‑disableLicenseExpirationCheck.
The final command converts the analyzer's report file into an html report:
- run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY}
- run: pvs-studio-analyzer trace -- make -j2 -C build/
- run: pvs-studio-analyzer analyze -j2 -l PVS.lic
-o PVS-Studio.log --disableLicenseExpirationCheck
- run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log
Once the tests are over, we save the reports:
- run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/
- store_artifacts:
path: ./PVS_Result
Here's the complete text of the .circleci/config.yml file:
version: 2.1
jobs:
build:
machine:
image: ubuntu-1604:201903-01
steps:
- checkout
- run: sudo -- sh -c "
add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends
&& add-apt-repository -y ppa:wsnipex/vaapi
&& add-apt-repository -y ppa:pulse-eight/libcec
&& apt-get update"
- run: sudo apt-get install -y automake autopoint
build-essential cmake curl default-jre gawk gdb
gdc gettext git-core gperf libasound2-dev libass-dev
libbluray-dev libbz2-dev libcap-dev libcdio-dev
libcec4-dev libcrossguid-dev libcurl3 libcurl4-openssl-dev
libdbus-1-dev libegl1-mesa-dev libfmt3-dev libfontconfig-dev
libfreetype6-dev libfribidi-dev libfstrcmp-dev libgif-dev
libgl1-mesa-dev libglu1-mesa-dev libiso9660-dev libjpeg-dev
liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev
libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev
libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev
libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev
libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev
libxrandr-dev libxrender-dev libxslt1-dev libxt-dev mesa-utils
nasm pmount python-dev python-imaging python-sqlite
rapidjson-dev swig unzip uuid-dev yasm zip zlib1g-dev wget
- run: wget -q -O - https://files.pvs-studio.com/etc/pubkey.txt
| sudo apt-key add –
&& sudo wget -O /etc/apt/sources.list.d/viva64.list
https://files.pvs-studio.com/etc/viva64.list
- run: sudo -- sh -c "apt-get update && apt-get install pvs-studio -y"
- run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local
- run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug ..
- run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY}
- run: pvs-studio-analyzer trace -- make -j2 -C build/
- run: pvs-studio-analyzer analyze -j2 -l PVS.lic
-o PVS-Studio.log --disableLicenseExpirationCheck
- run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log
- run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/
- store_artifacts:
path: ./PVS_Result
Once this file is uploaded to the repository, CircleCI will automatically start the build.
Once the job is finished, the files with the analysis results can be downloaded on the "Artifacts" tab.
OK, now let's take a look at some of the warnings output by the analyzer.
PVS-Studio warning: V504 It is highly probable that the semicolon ';' is missing after 'return' keyword. AdvancedSettings.cpp:1476
void CAdvancedSettings::SetExtraArtwork(const TiXmlElement* arttypes,
std::vector<std::string>& artworkMap)
{
if (!arttypes)
return
artworkMap.clear();
const TiXmlNode* arttype = arttypes->FirstChild("arttype");
....
}
The code formatting suggests the following execution logic:
But the missing ';' character breaks it all, and the actual execution logic is as follows:
To cut a long story short, this situation does look like a bug. After all, you hardly expect anyone to write expressions like return artworkMap.clear(); :).
PVS-Studio warnings:
int udf25::UDFGetAVDP( struct avdp_t *avdp)
{
....
uint32_t lastsector;
....
lastsector = 0; // <=
....
for(;;) {
....
if( lastsector ) { // <= V547
lbnum = lastsector;
terminate = 1;
} else {
//! @todo Find last sector of the disc (this is optional).
if( lastsector ) // <= V547
lbnum = lastsector - 256;
else
return 0;
}
}
....
}
Note the spots marked with // <=. The lastsector variable is assigned the value 0 and then used as a conditional expression in two if statements. Since the value doesn't change either in the loop or between the assignments, control will never get into then branches of both if statements.
However, it could also mean that the developers simply haven't implemented the intended functionality yet (note the @todo remark).
By the way, as you probably noticed, this snippet triggered three warnings at once. But even that many warnings for one piece of code wouldn't look convincing enough to some users, and they would keep believing that the analyzer is wrong... This aspect is discussed in detail in a post by one of my teammates: "One day from PVS-Studio user support" :).
PVS-Studio warning: V547 Expression 'values.size() != 2' is always false. GUIControlSettings.cpp:1174
bool CGUIControlRangeSetting::OnClick()
{
....
std::vector<CVariant> values;
SettingConstPtr listDefintion = settingList->GetDefinition();
switch (listDefintion->GetType())
{
case SettingType::Integer:
values.push_back(m_pSlider->
GetIntValue(CGUISliderControl::RangeSelectorLower));
values.push_back(m_pSlider->
GetIntValue(CGUISliderControl::RangeSelectorUpper));
break;
case SettingType::Number:
values.push_back(m_pSlider->
GetFloatValue(CGUISliderControl::RangeSelectorLower));
values.push_back(m_pSlider->
GetFloatValue(CGUISliderControl::RangeSelectorUpper));
break;
default:
return false;
}
if (values.size() != 2)
return false;
SetValid(CSettingUtils::SetList(settingList, values));
return IsValid();
}
The values.size() != 2 check is redundant here since this conditional expression will always evaluate to false. Indeed, if execution enters one of the case branches of the switch statement, two elements will be added to the vector, and since it was initially empty, its size will naturally become equal to 2; otherwise (i.e. if the default branch is executed), the method will return.
PVS-Studio warning: V547 Expression 'prio == 0x7fffffff' is always true. DBusReserve.cpp:57
bool CDBusReserve::AcquireDevice(const std::string& device)
{
....
int prio = INT_MAX;
....
res =
dbus_bus_request_name(
m_conn,
service.c_str(),
DBUS_NAME_FLAG_DO_NOT_QUEUE |
(prio == INT_MAX ? 0 : DBUS_NAME_FLAG_ALLOW_REPLACEMENT), // <=
error);
....
}
The prio variable is initialized to the INT_MAX value and then used as an operand of the ternary operator in the prio == INT_MAX comparison, though its value doesn't change after the initialization. It means the prio == INT_MAX expression is true and the ternary operator will always return 0.
PVS-Studio warnings:
CDVDOverlayImage(const CDVDOverlayImage& src)
: CDVDOverlay(src)
{
Data = (uint8_t*)malloc(src.linesize * src.height);
memcpy(data, src.data, src.linesize * src.height); // <=
if(src.palette)
{
palette = (uint32_t*)malloc(src.palette_colors * 4);
memcpy(palette, src.palette, src.palette_colors * 4); // <=
}
....
}
Both warnings have the same pattern: a pointer returned by the malloc function is used further in the memcpy function without being checked for NULL first.
Some may argue that malloc will never return a null pointer, and if it does, it would be better for the application to crash. It's a subject of a separate discussion, but whatever your opinion is, I recommend reading this post by my teammate: "Why it is important to check what the malloc function returned".
If you wish, you can customize the analyzer so that it doesn't assume that malloc could return a null pointer – this will keep it from outputting this type of warnings. More details can be found here.
PVS-Studio warning: V522 There might be dereferencing of a potential null pointer 'entry'. Check lines: 985, 981. emu_msvcrt.cpp:985
struct dirent *dll_readdir(DIR *dirp)
{
....
struct dirent *entry = NULL;
entry = (dirent*) malloc(sizeof(*entry));
if (dirData->curr_index < dirData->items.Size() + 2)
{
if (dirData->curr_index == 0)
strncpy(entry->d_name, ".\0", 2);
....
}
This example is similar to the previous one. The pointer returned by the malloc function is stored to the entry variable, and this variable is then used without a prior null check (entry->d_name).
PVS-Studio warning: V773 Visibility scope of the 'progressHandler' pointer was exited without releasing the memory. A memory leak is possible. PVRGUIChannelIconUpdater.cpp:94
void CPVRGUIChannelIconUpdater::SearchAndUpdateMissingChannelIcons() const
{
....
CPVRGUIProgressHandler* progressHandler =
new CPVRGUIProgressHandler(g_localizeStrings.Get(19286));
for (const auto& group : m_groups)
{
const std::vector<PVRChannelGroupMember> members = group->GetMembers();
int channelIndex = 0;
for (const auto& member : members)
{
progressHandler->UpdateProgress(member.channel->ChannelName(),
channelIndex++, members.size());
....
}
progressHandler->DestroyProgress();
}
The value of the progressHandler pointer was returned by the new operator. But there is no delete operator for this pointer. This means a memory leak.
PVS-Studio warning: V557 Array overrun is possible. The 'idx' index is pointing beyond array bound. PlayerCoreFactory.cpp:240
std::vector<CPlayerCoreConfig *> m_vecPlayerConfigs;
bool CPlayerCoreFactory::PlaysVideo(const std::string& player) const
{
CSingleLock lock(m_section);
size_t idx = GetPlayerIndex(player);
if (m_vecPlayerConfigs.empty() || idx > m_vecPlayerConfigs.size())
return false;
return m_vecPlayerConfigs[idx]->m_bPlaysVideo;
}
The if statement restricts the m_vecPlayerConfigs vector's size to a certain range by having the method return if the size-checking condition is true. As a result, when execution reaches the last return statement, the m_vecPlayerConfigs vector's size will be within the specified range, [1; idx]. But a couple of lines later, the program is indexing the vector at idx: m_vecPlayerConfigs[idx]->m_bPlaysVideo. It means that if idx is equal to the vector's size, we'll be indexing beyond the valid range.
Let's wind up this article with a couple of examples from the code of the Platinum library.
PVS-Studio warning: V542 Consider inspecting an odd type cast: 'bool' to 'char *'. PltCtrlPoint.cpp:1617
NPT_Result PLT_CtrlPoint::ProcessSubscribeResponse(...)
{
....
bool subscription = (request.GetMethod().ToUppercase() == "SUBSCRIBE");
....
NPT_String prefix = NPT_String::Format("
PLT_CtrlPoint::ProcessSubscribeResponse %ubscribe for
service \"%s\" (result = %d, status code = %d)",
(const char*)subscription?"S":"Uns", // <=
(const char*)service->GetServiceID(),
res,
response?response->GetStatusCode():0);
....
}
The developers have had wrong assumptions about the operations' precedence. What gets cast to const char* is not the result returned by the ternary operator (subscription ? "S" : "Uns") but the subscription variable. This looks strange, at the very least.
PVS-Studio warning: V560 A part of conditional expression is always false: c == '\t'. NptUtils.cpp:863
NPT_Result NPT_ParseMimeParameters(....)
{
....
case NPT_MIME_PARAMETER_PARSER_STATE_NEED_EQUALS:
if (c < ' ') return NPT_ERROR_INVALID_SYNTAX; // END or CTLs are invalid
if (c == ' ' || c == '\t') continue; // ignore leading whitespace
....
}
The code of the space character is 0x20, and the code of the tab character is 0x09. Therefore, the c == '\t' subexpression will always evaluate to false as this case is already covered by the c < ' ' check (which, if true, will cause the function to return).
As this article demonstrates, we successfully set up an analysis by PVS-Studio on yet another CI system (CircleCI). I invite you to download and try the analyzer on your own project. If you have any questions about the setting-up or use of PVS-Studio, don't hesitate to contact us – we'll be glad to help.
And, of course, we wish you bugless code. :)